Ticket #3639: 3639.2008-07-12.patch
File 3639.2008-07-12.patch, 38.8 KB (added by , 16 years ago) |
---|
-
django/views/generic/__init__.py
=== modified file 'django/views/generic/__init__.py'
1 class GenericViewError(Exception): 2 """A problem in a generic view.""" 3 pass -
django/views/generic/create_update.py
=== modified file 'django/views/generic/create_update.py'
1 from django.newforms.models import ModelFormMetaclass, ModelForm 2 from django.template import RequestContext, loader 3 from django.http import Http404, HttpResponse, HttpResponseRedirect 1 4 from django.core.xheaders import populate_xheaders 2 from django.template import loader3 from django import oldforms4 from django.db.models import FileField5 from django.contrib.auth.views import redirect_to_login6 from django.template import RequestContext7 from django.http import Http404, HttpResponse, HttpResponseRedirect8 5 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured 9 6 from django.utils.translation import ugettext 10 11 def create_object(request, model, template_name=None, 7 from django.contrib.auth.views import redirect_to_login 8 from django.views.generic import GenericViewError 9 10 11 def deprecate_follow(follow): 12 """ 13 Issues a DeprecationWarning if follow is anything but None. 14 15 The old Manipulator-based forms used a follow argument that is no longer 16 needed for newforms-based forms. 17 """ 18 if follow is not None: 19 import warning 20 msg = ("Generic views have been changed to use newforms, and the" 21 "'follow' argument is no longer used. Please update your code" 22 "to not use the 'follow' argument.") 23 warning.warn(msg, DeprecationWarning, stacklevel=3) 24 25 26 def apply_extra_context(extra_context, context): 27 """ 28 Adds items from extra_context dict to context. If a value in extra_context 29 is callable, then it is called and the result is added to context. 30 """ 31 for key, value in extra_context.iteritems(): 32 if callable(value): 33 context[key] = value() 34 else: 35 context[key] = value 36 37 38 def get_model_and_form_class(model, form_class): 39 """ 40 Returns a model and form class based on the model and form_class 41 parameters that were passed to the generic view. 42 43 If ``form_class`` is given then its associated model will be returned along 44 with ``form_class`` itself. Otherwise, if ``model`` is given, ``model`` 45 itself will be returned along with a ``ModelForm`` class created from 46 ``model``. 47 """ 48 if form_class: 49 return form_class._meta.model, form_class 50 if model: 51 # The inner Meta class fails if model = model is used for some reason. 52 tmp_model = model 53 # TODO: we should be able to construct a ModelForm without creating 54 # and passing in a temporary inner class. 55 class Meta: 56 model = tmp_model 57 class_name = model.__name__ + 'Form' 58 form_class = ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta}) 59 return model, form_class 60 raise GenericViewError("Generic view must be called with either a model or" 61 " form_class argument.") 62 63 64 def redirect(post_save_redirect, obj): 65 """ 66 Returns a HttpResponseRedirect to ``post_save_redirect``. 67 68 ``post_save_redirect`` should be a string, and can contain named string- 69 substitution place holders of ``obj`` field names. 70 71 If ``post_save_redirect`` is None, then redirect to ``obj``'s URL returned 72 by ``get_absolute_url()``. If ``obj`` has no ``get_absolute_url`` method, 73 then raise ImproperlyConfigured. 74 75 This method is meant to handle the post_save_redirect parameter to the 76 ``create_object`` and ``update_object`` views. 77 """ 78 if post_save_redirect: 79 return HttpResponseRedirect(post_save_redirect % obj.__dict__) 80 elif hasattr(obj, 'get_absolute_url'): 81 return HttpResponseRedirect(obj.get_absolute_url()) 82 else: 83 raise ImproperlyConfigured( 84 "No URL to redirect to. Either pass a post_save_redirect" 85 " parameter to the generic view or define a get_absolute_url" 86 " method on the Model.") 87 88 89 def lookup_object(model, object_id, slug, slug_field): 90 """ 91 Return the ``model`` object with the passed ``object_id``. If 92 ``object_id`` is None, then return the the object whose ``slug_field`` 93 equals the passed ``slug``. If ``slug`` and ``slug_field`` are not passed, 94 then raise Http404 exception. 95 """ 96 lookup_kwargs = {} 97 if object_id: 98 lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id 99 elif slug and slug_field: 100 lookup_kwargs['%s__exact' % slug_field] = slug 101 else: 102 raise GenericViewError( 103 "Generic view must be called with either an object_id or a" 104 " slug/slug_field.") 105 try: 106 return model.objects.get(**lookup_kwargs) 107 except ObjectDoesNotExist: 108 raise Http404("No %s found for %s" 109 % (model._meta.verbose_name, lookup_kwargs)) 110 111 112 def create_object(request, model=None, template_name=None, 12 113 template_loader=loader, extra_context=None, post_save_redirect=None, 13 login_required=False, follow=None, context_processors=None): 114 login_required=False, follow=None, context_processors=None, 115 form_class=None): 14 116 """ 15 117 Generic object-creation function. 16 118 17 119 Templates: ``<app_label>/<model_name>_form.html`` 18 120 Context: 19 121 form 20 the form wrapperfor the object122 the form for the object 21 123 """ 124 deprecate_follow(follow) 22 125 if extra_context is None: extra_context = {} 23 126 if login_required and not request.user.is_authenticated(): 24 127 return redirect_to_login(request.path) 25 128 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 129 model, form_class = get_model_and_form_class(model, form_class) 130 if request.method == 'POST': 131 form = form_class(request.POST, request.FILES) 132 if form.is_valid(): 133 new_object = form.save() 42 134 if request.user.is_authenticated(): 43 135 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name}) 44 45 # Redirect to the new object: first by trying post_save_redirect, 46 # then by obj.get_absolute_url; fail if neither works. 47 if post_save_redirect: 48 return HttpResponseRedirect(post_save_redirect % new_object.__dict__) 49 elif hasattr(new_object, 'get_absolute_url'): 50 return HttpResponseRedirect(new_object.get_absolute_url()) 51 else: 52 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 136 return redirect(post_save_redirect, new_object) 53 137 else: 54 # No POST, so we want a brand new form without any data or errors 55 errors = {} 56 new_data = manipulator.flatten_data() 138 form = form_class() 57 139 58 # Create the FormWrapper, template, context, response 59 form = oldforms.FormWrapper(manipulator, new_data, errors) 140 # Create the template, context, response 60 141 if not template_name: 61 142 template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower()) 62 143 t = template_loader.get_template(template_name) 63 144 c = RequestContext(request, { 64 145 'form': form, 65 146 }, context_processors) 66 for key, value in extra_context.items(): 67 if callable(value): 68 c[key] = value() 69 else: 70 c[key] = value 147 apply_extra_context(extra_context, c) 71 148 return HttpResponse(t.render(c)) 72 149 73 def update_object(request, model, object_id=None, slug=None, 150 151 def update_object(request, model=None, object_id=None, slug=None, 74 152 slug_field='slug', template_name=None, template_loader=loader, 75 153 extra_context=None, post_save_redirect=None, 76 154 login_required=False, follow=None, context_processors=None, 77 template_object_name='object' ):155 template_object_name='object', form_class=None): 78 156 """ 79 157 Generic object-update function. 80 158 81 159 Templates: ``<app_label>/<model_name>_form.html`` 82 160 Context: 83 161 form 84 the form wrapperfor the object162 the form for the object 85 163 object 86 164 the original object being edited 87 165 """ 166 deprecate_follow(follow) 88 167 if extra_context is None: extra_context = {} 89 168 if login_required and not request.user.is_authenticated(): 90 169 return redirect_to_login(request.path) 91 170 92 # Look up the object to be edited 93 lookup_kwargs = {} 94 if object_id: 95 lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id 96 elif slug and slug_field: 97 lookup_kwargs['%s__exact' % slug_field] = slug 98 else: 99 raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field") 100 try: 101 object = model.objects.get(**lookup_kwargs) 102 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) 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) 115 171 model, form_class = get_model_and_form_class(model, form_class) 172 obj = lookup_object(model, object_id, slug, slug_field) 173 174 if request.method == 'POST': 175 form = form_class(request.POST, request.FILES, instance=obj) 176 if form.is_valid(): 177 obj = form.save() 116 178 if request.user.is_authenticated(): 117 179 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name}) 118 119 # Do a post-after-redirect so that reload works, etc. 120 if post_save_redirect: 121 return HttpResponseRedirect(post_save_redirect % object.__dict__) 122 elif hasattr(object, 'get_absolute_url'): 123 return HttpResponseRedirect(object.get_absolute_url()) 124 else: 125 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 180 return redirect(post_save_redirect, obj) 126 181 else: 127 errors = {} 128 # This makes sure the form acurate represents the fields of the place. 129 new_data = manipulator.flatten_data() 182 form = form_class(instance=obj) 130 183 131 form = oldforms.FormWrapper(manipulator, new_data, errors)132 184 if not template_name: 133 185 template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower()) 134 186 t = template_loader.get_template(template_name) 135 187 c = RequestContext(request, { 136 188 'form': form, 137 template_object_name: obj ect,189 template_object_name: obj, 138 190 }, context_processors) 139 for key, value in extra_context.items(): 140 if callable(value): 141 c[key] = value() 142 else: 143 c[key] = value 191 apply_extra_context(extra_context, c) 144 192 response = HttpResponse(t.render(c)) 145 populate_xheaders(request, response, model, getattr(obj ect, object._meta.pk.attname))193 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname)) 146 194 return response 147 195 148 def delete_object(request, model, post_delete_redirect, 149 object_id=None, slug=None, slug_field='slug', template_name=None, 150 template_loader=loader, extra_context=None, 151 login_required=False, context_processors=None, template_object_name='object'): 196 197 def delete_object(request, model, post_delete_redirect, object_id=None, 198 slug=None, slug_field='slug', template_name=None, 199 template_loader=loader, extra_context=None, login_required=False, 200 context_processors=None, template_object_name='object'): 152 201 """ 153 202 Generic object-delete function. 154 203 … … 165 214 if login_required and not request.user.is_authenticated(): 166 215 return redirect_to_login(request.path) 167 216 168 # Look up the object to be edited 169 lookup_kwargs = {} 170 if object_id: 171 lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id 172 elif slug and slug_field: 173 lookup_kwargs['%s__exact' % slug_field] = slug 174 else: 175 raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field") 176 try: 177 object = model._default_manager.get(**lookup_kwargs) 178 except ObjectDoesNotExist: 179 raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs) 217 obj = lookup_object(model, object_id, slug, slug_field) 180 218 181 219 if request.method == 'POST': 182 obj ect.delete()220 obj.delete() 183 221 if request.user.is_authenticated(): 184 222 request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}) 185 223 return HttpResponseRedirect(post_delete_redirect) … … 188 226 template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower()) 189 227 t = template_loader.get_template(template_name) 190 228 c = RequestContext(request, { 191 template_object_name: obj ect,229 template_object_name: obj, 192 230 }, context_processors) 193 for key, value in extra_context.items(): 194 if callable(value): 195 c[key] = value() 196 else: 197 c[key] = value 231 apply_extra_context(extra_context, c) 198 232 response = HttpResponse(t.render(c)) 199 populate_xheaders(request, response, model, getattr(obj ect, object._meta.pk.attname))233 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname)) 200 234 return response -
docs/generic_views.txt
=== modified file 'docs/generic_views.txt'
701 701 query string parameter (via ``GET``) or a ``page`` variable specified in 702 702 the URLconf. See `Notes on pagination`_ below. 703 703 704 * ``page``: The current page number, as an integer. This is 1-based. 704 * ``page``: The current page number, as an integer. This is 1-based. 705 705 See `Notes on pagination`_ below. 706 706 707 707 * ``template_name``: The full name of a template to use in rendering the … … 809 809 810 810 /objects/?page=3 811 811 812 * To loop over all the available page numbers, use the ``page_range`` 813 variable. You can iterate over the list provided by ``page_range`` 812 * To loop over all the available page numbers, use the ``page_range`` 813 variable. You can iterate over the list provided by ``page_range`` 814 814 to create a link to every page of results. 815 815 816 816 These values and lists are 1-based, not 0-based, so the first page would be 817 represented as page ``1``. 817 represented as page ``1``. 818 818 819 819 For more on pagination, read the `pagination documentation`_. 820 820 821 821 .. _`pagination documentation`: ../pagination/ 822 822 823 **New in Django development version:** 823 **New in Django development version:** 824 824 825 825 As a special case, you are also permitted to use ``last`` as a value for 826 826 ``page``:: 827 827 828 828 /objects/?page=last 829 829 830 This allows you to access the final page of results without first having to 830 This allows you to access the final page of results without first having to 831 831 determine how many pages there are. 832 832 833 833 Note that ``page`` *must* be either a valid page number or the value ``last``; … … 906 906 The ``django.views.generic.create_update`` module contains a set of functions 907 907 for creating, editing and deleting objects. 908 908 909 **New in Django development version:** 910 911 ``django.views.generic.create_update.create_object`` and 912 ``django.views.generic.create_update.update_object`` now use `newforms`_ to 913 build and display the form. 914 915 .. _newforms: ../newforms/ 916 909 917 ``django.views.generic.create_update.create_object`` 910 918 ---------------------------------------------------- 911 919 912 920 **Description:** 913 921 914 922 A page that displays a form for creating an object, redisplaying the form with 915 validation errors (if there are any) and saving the object. This uses the 916 automatic manipulators that come with Django models. 923 validation errors (if there are any) and saving the object. 917 924 918 925 **Required arguments:** 919 926 920 * ``model``: The Django model class of the object that the form will 921 create. 927 * Either ``form_class`` or ``model`` is required. 928 929 If you provide ``form_class``, it should be a 930 ``django.newforms.ModelForm`` subclass. Use this argument when you need 931 to customize the model's form. See the `ModelForm docs`_ for more 932 information. 933 934 Otherwise, ``model`` should be a Django model class and the form used 935 will be a standard ``ModelForm`` for ``model``. 922 936 923 937 **Optional arguments:** 924 938 … … 959 973 960 974 In addition to ``extra_context``, the template's context will be: 961 975 962 * ``form``: A ``django. oldforms.FormWrapper`` instance representing the form963 for editing the object. This lets you refer to form fields easily in the976 * ``form``: A ``django.newforms.ModelForm`` instance representing the form 977 for creating the object. This lets you refer to form fields easily in the 964 978 template system. 965 979 966 For example, if ``model``has two fields, ``name`` and ``address``::980 For example, if the model has two fields, ``name`` and ``address``:: 967 981 968 982 <form action="" method="post"> 969 <p> <label for="id_name">Name:</label>{{ form.name }}</p>970 <p> <label for="id_address">Address:</label>{{ form.address }}</p>983 <p>{{ form.name.label_tag }} {{ form.name }}</p> 984 <p>{{ form.address.label_tag }} {{ form.address }}</p> 971 985 </form> 972 986 973 See the ` manipulator and formfield documentation`_ for more information974 about using ``FormWrapper`` objects in templates.987 See the `newforms documentation`_ for more information about using 988 ``Form`` objects in templates. 975 989 976 990 .. _authentication system: ../authentication/ 977 .. _manipulator and formfield documentation: ../forms/ 991 .. _ModelForm docs: ../newforms/modelforms 992 .. _newforms documentation: ../newforms/ 978 993 979 994 ``django.views.generic.create_update.update_object`` 980 995 ---------------------------------------------------- … … 987 1002 988 1003 **Required arguments:** 989 1004 990 * ``model``: The Django model class of the object that the form will 991 create. 1005 * Either ``form_class`` or ``model`` is required. 1006 1007 If you provide ``form_class``, it should be a 1008 ``django.newforms.ModelForm`` subclass. Use this argument when you need 1009 to customize the model's form. See the `ModelForm docs`_ for more 1010 information. 1011 1012 Otherwise, ``model`` should be a Django model class and the form used 1013 will be a standard ``ModelForm`` for ``model``. 992 1014 993 1015 * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required. 994 1016 … … 1041 1063 1042 1064 In addition to ``extra_context``, the template's context will be: 1043 1065 1044 * ``form``: A ``django. oldforms.FormWrapper`` instance representing the form1066 * ``form``: A ``django.newforms.ModelForm`` instance representing the form 1045 1067 for editing the object. This lets you refer to form fields easily in the 1046 1068 template system. 1047 1069 1048 For example, if ``model``has two fields, ``name`` and ``address``::1070 For example, if the model has two fields, ``name`` and ``address``:: 1049 1071 1050 1072 <form action="" method="post"> 1051 <p> <label for="id_name">Name:</label>{{ form.name }}</p>1052 <p> <label for="id_address">Address:</label>{{ form.address }}</p>1073 <p>{{ form.name.label_tag }} {{ form.name }}</p> 1074 <p>{{ form.address.label_tag }} {{ form.address }}</p> 1053 1075 </form> 1054 1076 1055 See the ` manipulator and formfield documentation`_ for more information1056 about using ``FormWrapper`` objects in templates.1077 See the `newforms documentation`_ for more information about using 1078 ``Form`` objects in templates. 1057 1079 1058 1080 * ``object``: The original object being edited. This variable's name 1059 1081 depends on the ``template_object_name`` parameter, which is ``'object'`` -
tests/regressiontests/views/fixtures/testdata.json
=== modified file 'tests/regressiontests/views/fixtures/testdata.json'
1 1 [ 2 2 { 3 "pk": "1", 4 "model": "auth.user", 5 "fields": { 6 "username": "testclient", 7 "first_name": "Test", 8 "last_name": "Client", 9 "is_active": true, 10 "is_superuser": false, 11 "is_staff": false, 12 "last_login": "2006-12-17 07:03:31", 13 "groups": [], 14 "user_permissions": [], 15 "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161", 16 "email": "testclient@example.com", 17 "date_joined": "2006-12-17 07:03:31" 18 } 19 }, 20 { 3 21 "pk": 1, 4 22 "model": "views.article", 5 23 "fields": { … … 29 47 "date_created": "3000-01-01 21:22:23" 30 48 } 31 49 }, 32 50 { 51 "pk": 1, 52 "model": "views.urlarticle", 53 "fields": { 54 "author": 1, 55 "title": "Old Article", 56 "slug": "old_article", 57 "date_created": "2001-01-01 21:22:23" 58 } 59 }, 33 60 { 34 61 "pk": 1, 35 62 "model": "views.author", -
tests/regressiontests/views/models.py
=== modified file 'tests/regressiontests/views/models.py'
1 1 """ 2 Regression tests for Django built-in views 2 Regression tests for Django built-in views. 3 3 """ 4 4 5 5 from django.db import models 6 from django.conf import settings 6 7 7 8 8 class Author(models.Model): 9 9 name = models.CharField(max_length=100) … … 15 15 return '/views/authors/%s/' % self.id 16 16 17 17 18 class Article(models.Model): 18 class BaseArticle(models.Model): 19 """ 20 An abstract article Model so that we can create article models with and 21 without a get_absolute_url method (for create_update generic views tests). 22 """ 19 23 title = models.CharField(max_length=100) 20 24 slug = models.SlugField() 21 25 author = models.ForeignKey(Author) 22 26 date_created = models.DateTimeField() 23 27 28 class Meta: 29 abstract = True 30 24 31 def __unicode__(self): 25 32 return self.title 26 33 34 35 class Article(BaseArticle): 36 pass 37 38 39 class UrlArticle(BaseArticle): 40 """ 41 An Article class with a get_absolute_url defined. 42 """ 43 def get_absolute_url(self): 44 return '/urlarticles/%s/' % self.slug -
tests/regressiontests/views/tests/__init__.py
=== modified file 'tests/regressiontests/views/tests/__init__.py'
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 * -
tests/regressiontests/views/tests/generic/create_update.py
=== added file 'tests/regressiontests/views/tests/generic/create_update.py'
1 import datetime 2 3 from django.test import TestCase 4 from django.core.exceptions import ImproperlyConfigured 5 from regressiontests.views.models import Article, UrlArticle 6 7 8 class CreateObjectTest(TestCase): 9 10 fixtures = ['testdata.json'] 11 12 def test_login_required_view(self): 13 """ 14 Verifies that an unauthenticated user attempting to access a 15 login_required view gets redirected to the login page and that 16 an authenticated user is let through. 17 """ 18 view_url = '/views/create_update/member/create/article/' 19 response = self.client.get(view_url) 20 self.assertRedirects(response, '/accounts/login/?next=%s' % view_url) 21 # Now login and try again. 22 login = self.client.login(username='testclient', password='password') 23 self.failUnless(login, 'Could not log in') 24 response = self.client.get(view_url) 25 self.assertEqual(response.status_code, 200) 26 self.assertTemplateUsed(response, 'views/article_form.html') 27 28 def test_create_article_display_page(self): 29 """ 30 Ensures the generic view returned the page and contains a form. 31 """ 32 view_url = '/views/create_update/create/article/' 33 response = self.client.get(view_url) 34 self.assertEqual(response.status_code, 200) 35 self.assertTemplateUsed(response, 'views/article_form.html') 36 if not response.context.get('form'): 37 self.fail('No form found in the response.') 38 39 def test_create_article_with_errors(self): 40 """ 41 POSTs a form that contains validation errors. 42 """ 43 view_url = '/views/create_update/create/article/' 44 num_articles = Article.objects.count() 45 response = self.client.post(view_url, { 46 'title': 'My First Article', 47 }) 48 self.assertFormError(response, 'form', 'slug', [u'This field is required.']) 49 self.assertTemplateUsed(response, 'views/article_form.html') 50 self.assertEqual(num_articles, Article.objects.count(), 51 "Number of Articles should not have changed.") 52 53 def test_create_custom_save_article(self): 54 """ 55 Creates a new article using a custom form class with a save method 56 that alters the slug entered. 57 """ 58 view_url = '/views/create_update/create_custom/article/' 59 response = self.client.post(view_url, { 60 'title': 'Test Article', 61 'slug': 'this-should-get-replaced', 62 'author': 1, 63 'date_created': datetime.datetime(2007, 6, 25), 64 }) 65 self.assertRedirects(response, 66 '/views/create_update/view/article/some-other-slug/', 67 target_status_code=404) 68 69 70 class UpdateDeleteObjectTest(TestCase): 71 72 fixtures = ['testdata.json'] 73 74 def test_update_object_form_display(self): 75 """ 76 Verifies that the form was created properly and with initial values. 77 """ 78 response = self.client.get('/views/create_update/update/article/old_article/') 79 self.assertTemplateUsed(response, 'views/article_form.html') 80 self.assertEquals(unicode(response.context['form']['title']), 81 u'<input id="id_title" type="text" name="title" value="Old Article" maxlength="100" />') 82 83 def test_update_object(self): 84 """ 85 Verifies the updating of an Article. 86 """ 87 response = self.client.post('/views/create_update/update/article/old_article/', { 88 'title': 'Another Article', 89 'slug': 'another-article-slug', 90 'author': 1, 91 'date_created': datetime.datetime(2007, 6, 25), 92 }) 93 article = Article.objects.get(pk=1) 94 self.assertEquals(article.title, "Another Article") 95 96 def test_delete_object_confirm(self): 97 """ 98 Verifies the confirm deletion page is displayed using a GET. 99 """ 100 response = self.client.get('/views/create_update/delete/article/old_article/') 101 self.assertTemplateUsed(response, 'views/article_confirm_delete.html') 102 103 def test_delete_object(self): 104 """ 105 Verifies the object actually gets deleted on a POST. 106 """ 107 view_url = '/views/create_update/delete/article/old_article/' 108 response = self.client.post(view_url) 109 try: 110 Article.objects.get(slug='old_article') 111 except Article.DoesNotExist: 112 pass 113 else: 114 self.fail('Object was not deleted.') 115 116 117 class PostSaveRedirectTests(TestCase): 118 """ 119 Verifies that the views redirect to the correct locations depending on 120 if a post_save_redirect was passed and a get_absolute_url method exists 121 on the Model. 122 """ 123 124 fixtures = ['testdata.json'] 125 article_model = Article 126 127 create_url = '/views/create_update/create/article/' 128 update_url = '/views/create_update/update/article/old_article/' 129 delete_url = '/views/create_update/delete/article/old_article/' 130 131 create_redirect = '/views/create_update/view/article/my-first-article/' 132 update_redirect = '/views/create_update/view/article/another-article-slug/' 133 delete_redirect = '/views/create_update/' 134 135 def test_create_article(self): 136 num_articles = self.article_model.objects.count() 137 response = self.client.post(self.create_url, { 138 'title': 'My First Article', 139 'slug': 'my-first-article', 140 'author': '1', 141 'date_created': datetime.datetime(2007, 6, 25), 142 }) 143 self.assertRedirects(response, self.create_redirect, 144 target_status_code=404) 145 self.assertEqual(num_articles + 1, self.article_model.objects.count(), 146 "A new Article should have been created.") 147 148 def test_update_article(self): 149 num_articles = self.article_model.objects.count() 150 response = self.client.post(self.update_url, { 151 'title': 'Another Article', 152 'slug': 'another-article-slug', 153 'author': 1, 154 'date_created': datetime.datetime(2007, 6, 25), 155 }) 156 self.assertRedirects(response, self.update_redirect, 157 target_status_code=404) 158 self.assertEqual(num_articles, self.article_model.objects.count(), 159 "A new Article should not have been created.") 160 161 def test_delete_article(self): 162 num_articles = self.article_model.objects.count() 163 response = self.client.post(self.delete_url) 164 self.assertRedirects(response, self.delete_redirect, 165 target_status_code=404) 166 self.assertEqual(num_articles - 1, self.article_model.objects.count(), 167 "An Article should have been deleted.") 168 169 170 class NoPostSaveNoAbsoluteUrl(PostSaveRedirectTests): 171 """ 172 Tests that when no post_save_redirect is passed and no get_absolute_url 173 method exists on the Model that the view raises an ImproperlyConfigured 174 error. 175 """ 176 177 create_url = '/views/create_update/no_redirect/create/article/' 178 update_url = '/views/create_update/no_redirect/update/article/old_article/' 179 180 def test_create_article(self): 181 self.assertRaises(ImproperlyConfigured, 182 super(NoPostSaveNoAbsoluteUrl, self).test_create_article) 183 184 def test_update_article(self): 185 self.assertRaises(ImproperlyConfigured, 186 super(NoPostSaveNoAbsoluteUrl, self).test_update_article) 187 188 def test_delete_article(self): 189 """ 190 The delete_object view requires a post_delete_redirect, so skip testing 191 here. 192 """ 193 pass 194 195 196 class AbsoluteUrlNoPostSave(PostSaveRedirectTests): 197 """ 198 Tests that the views redirect to the Model's get_absolute_url when no 199 post_save_redirect is passed. 200 """ 201 202 # Article model with get_absolute_url method. 203 article_model = UrlArticle 204 205 create_url = '/views/create_update/no_url/create/article/' 206 update_url = '/views/create_update/no_url/update/article/old_article/' 207 208 create_redirect = '/urlarticles/my-first-article/' 209 update_redirect = '/urlarticles/another-article-slug/' 210 211 def test_delete_article(self): 212 """ 213 The delete_object view requires a post_delete_redirect, so skip testing 214 here. 215 """ 216 pass -
tests/regressiontests/views/urls.py
=== modified file 'tests/regressiontests/views/urls.py'
5 5 from models import * 6 6 import views 7 7 8 8 9 base_dir = path.dirname(path.abspath(__file__)) 9 10 media_dir = path.join(base_dir, 'media') 10 11 locale_dir = path.join(base_dir, 'locale') … … 14 15 'packages': ('regressiontests.views',), 15 16 } 16 17 17 date_based_info_dict = { 18 'queryset': Article.objects.all(), 19 'date_field': 'date_created', 20 'month_format': '%m', 21 } 18 date_based_info_dict = { 19 'queryset': Article.objects.all(), 20 'date_field': 'date_created', 21 'month_format': '%m', 22 } 22 23 23 24 urlpatterns = patterns('', 24 25 (r'^$', views.index_page), 25 26 26 27 # Default views 27 28 (r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'), 28 29 (r'^non_existing_url/', 'django.views.defaults.page_not_found'), 29 30 (r'^server_error/', 'django.views.defaults.server_error'), 30 31 31 32 # i18n views 32 (r'^i18n/', include('django.conf.urls.i18n')), 33 (r'^i18n/', include('django.conf.urls.i18n')), 33 34 (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict), 34 35 35 36 # Static views 36 37 (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}), 37 38 # Date-based generic views 39 (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$', 40 'django.views.generic.date_based.object_detail', 41 dict(slug_field='slug', **date_based_info_dict)), 42 (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/allow_future/$', 43 'django.views.generic.date_based.object_detail', 44 dict(allow_future=True, slug_field='slug', **date_based_info_dict)), 45 (r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$', 46 'django.views.generic.date_based.archive_month', 47 date_based_info_dict), 38 ) 39 40 # Date-based generic views. 41 urlpatterns += patterns('django.views.generic.date_based', 42 (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$', 43 'object_detail', 44 dict(slug_field='slug', **date_based_info_dict)), 45 (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/allow_future/$', 46 'object_detail', 47 dict(allow_future=True, slug_field='slug', **date_based_info_dict)), 48 (r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$', 49 'archive_month', 50 date_based_info_dict), 51 ) 52 53 # crud generic views. 54 55 urlpatterns += patterns('django.views.generic.create_update', 56 (r'^create_update/member/create/article/$', 'create_object', 57 dict(login_required=True, model=Article)), 58 (r'^create_update/create/article/$', 'create_object', 59 dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', 60 model=Article)), 61 (r'^create_update/update/article/(?P<slug>[-\w]+)/$', 'update_object', 62 dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', 63 slug_field='slug', model=Article)), 64 (r'^create_update/create_custom/article/$', views.custom_create), 65 (r'^create_update/delete/article/(?P<slug>[-\w]+)/$', 'delete_object', 66 dict(post_delete_redirect='/views/create_update/', slug_field='slug', 67 model=Article)), 68 69 # No post_save_redirect and no get_absolute_url on model. 70 (r'^create_update/no_redirect/create/article/$', 'create_object', 71 dict(model=Article)), 72 (r'^create_update/no_redirect/update/article/(?P<slug>[-\w]+)/$', 73 'update_object', dict(slug_field='slug', model=Article)), 74 75 # get_absolute_url on model, but no passed post_save_redirect. 76 (r'^create_update/no_url/create/article/$', 'create_object', 77 dict(model=UrlArticle)), 78 (r'^create_update/no_url/update/article/(?P<slug>[-\w]+)/$', 79 'update_object', dict(slug_field='slug', model=UrlArticle)), 48 80 ) -
tests/regressiontests/views/views.py
=== modified file 'tests/regressiontests/views/views.py'
1 1 from django.http import HttpResponse 2 import django.newforms as forms 3 from django.views.generic.create_update import create_object 4 5 from models import Article 6 2 7 3 8 def index_page(request): 4 9 """Dummy index page""" 5 10 return HttpResponse('<html><body>Dummy page</body></html>') 11 12 13 def custom_create(request): 14 """ 15 Calls create_object generic view with a custom form class. 16 """ 17 class SlugChangingArticleForm(forms.ModelForm): 18 """Custom form class to overwrite the slug.""" 19 20 class Meta: 21 model = Article 22 23 def save(self, *args, **kwargs): 24 self.cleaned_data['slug'] = 'some-other-slug' 25 return super(SlugChangingArticleForm, self).save(*args, **kwargs) 26 27 return create_object(request, 28 post_save_redirect='/views/create_update/view/article/%(slug)s/', 29 form_class=SlugChangingArticleForm) -
tests/templates/views/article_confirm_delete.html
=== added file 'tests/templates/views/article_confirm_delete.html'
1 This template intentionally left blank 2 No newline at end of file -
tests/templates/views/article_detail.html
=== modified file 'tests/templates/views/article_detail.html'
1 This template intentionally left blank 2 No newline at end of file 1 Article detail template. -
tests/templates/views/article_form.html
=== added file 'tests/templates/views/article_form.html'
1 Article form template. 2 3 {{ form.errors }} -
tests/templates/views/urlarticle_detail.html
=== added file 'tests/templates/views/urlarticle_detail.html'
1 UrlArticle detail template. -
tests/templates/views/urlarticle_form.html
=== added file 'tests/templates/views/urlarticle_form.html'
1 UrlArticle form template. 2 3 {{ form.errors }}