Ticket #6735: generic-views.2.diff
File generic-views.2.diff, 21.3 KB (added by , 17 years ago) |
---|
-
new file django/views/generic/base.py
diff --git a/django/views/generic/base.py b/django/views/generic/base.py new file mode 100644 index 0000000..ab90d91
- + 1 from django.http import Http404, HttpResponse 2 from django.core.exceptions import ObjectDoesNotExist 3 from django.template import RequestContext 4 5 class BaseView(object): 6 """ 7 Base class for creating class based view objects. 8 """ 9 def __init__(self, queryset): 10 self.queryset = queryset 11 self.model = queryset.model 12 13 def get_query_set(self, request): 14 """ 15 Hook to provide a custom queryset based on the request. 16 """ 17 return self.queryset._clone() 18 19 def get_template(self, request, obj=None): 20 """ 21 Returns a template object used to render this view. 22 """ 23 raise NotImplementedError(u'Views must implement their own get_template method.') 24 25 def render_response(self, request, template, context_vars, mimetype=None): 26 """ 27 Returns an HttpResponse for the given request, template object, 28 dictionary of context variables, and optional mimetype. 29 """ 30 context = RequestContext(request, context_vars) 31 template = template.render(context) 32 return HttpResponse(template, mimetype=mimetype) 33 34 class BaseDetailView(BaseView): 35 def __init__(self, queryset, slug_field='slug'): 36 self.slug_field = slug_field 37 super(BaseDetailView, self).__init__(queryset) 38 39 def get_object(self, request, object_pk=None, slug=None): 40 """ 41 Returns the object to be viewed, changed or deleted, or raises a 404 42 if it doesn't exist. 43 """ 44 # Look up the object to be changed or deleted 45 lookup_kwargs = {} 46 if object_pk: 47 lookup_kwargs['pk'] = object_pk 48 elif slug and self.slug_field: 49 lookup_kwargs[self.slug_field] = slug 50 else: 51 raise AttributeError("Generic view must be called with either an object_pk or a slug/slug_field") 52 try: 53 return self.get_query_set(request).get(**lookup_kwargs) 54 except ObjectDoesNotExist: 55 raise Http404, "No %s found for %s" % (self.model._meta.verbose_name, lookup_kwargs) -
django/views/generic/create_update.py
diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py index 46e92fe..fc88f13 100644
a b 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 from django.newforms.models import ModelFormMetaclass, ModelForm 11 from django.views.generic.base import BaseDetailView 12 13 class EditView(BaseDetailView): 14 """ 15 Create/Update generic view. 16 17 Templates: ``<app_label>/<model_name>_form.html`` 18 Context: 19 form 20 the ``ModelForm`` instance for the object 21 object 22 the ``Model`` instance being changed 23 """ 24 def __init__(self, queryset, slug_field='slug', post_save_redirect=None): 25 self.post_save_redirect = post_save_redirect 26 super(EditView, self).__init__(queryset, slug_field) 27 28 def __call__(self, request, object_pk=None, slug=None): 29 # If we didn't get object_pk or slug, assume this is an add view. 30 if object_pk is None and slug is None: 31 obj = None 32 else: 33 obj = self.get_object(request, object_pk, slug) 34 Form = self.get_form(request) 35 if request.POST: 36 form = Form(request.POST, request.FILES, instance=obj) 37 if form.is_valid(): 38 new_obj = self.save_form(request, form) 39 return self.on_success(request, obj, new_obj) 40 else: 41 form = Form() 42 context_vars = {'object': obj, 'form': form} 43 template = self.get_template(request, obj) 44 return self.render_response(request, template, context_vars) 45 46 def get_form(self, request): 47 """ 48 Returns a ``ModelForm`` class to be used in this view. 49 """ 50 # TODO: we should be able to construct a ModelForm without creating 51 # and passing in a temporary inner class 52 class Meta: 53 model = self.model 54 class_name = self.model.__name__ + 'Form' 55 return ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta}) 56 57 def get_template(self, request, obj=None): 58 """ 59 Returns the template to be used when rendering this view. Those who 60 wish to use a custom template loader should do so here. 61 """ 62 opts = self.model._meta 63 template_name = "%s/%s_form.html" % (opts.app_label, opts.object_name.lower()) 64 return loader.get_template(template_name) 65 66 def get_message(self, request, obj, new_obj): 67 # If the primary ke of the original object is None, we just created an 68 # object, otherwise, we updated one. 69 if obj.pk is None: 70 return ugettext("The %s was created successfully.") % self.model._meta.verbose_name 71 return ugettext("The %s was updated successfully.") % self.model._meta.verbose_name 72 73 def save_form(self, request, form): 74 """ 75 Saves and returns the object represented by the given form. This 76 method will only be called if the form is valid. 77 """ 78 return form.save() 79 80 def on_success(self, request, obj, new_obj): 81 """ 82 Returns an HttpResonse, generally an HttpResponse redirect. This will 83 be the final return value of the view and will only be called if the 84 object was saved successfuly. 85 """ 86 # We can only do messaging for authenticated users right now 87 if request.user.is_authenticated(): 88 message = self.get_message(request, new_obj) 89 request.user.message_set.create(message=message) 90 # Redirect to the new object: first by trying post_save_redirect, 91 # then by obj.get_absolute_url; fail if neither works. 92 if self.post_save_redirect: 93 return HttpResponseRedirect(self.post_save_redirect % new_obj.__dict__) 94 elif hasattr(new_obj, 'get_absolute_url'): 95 return HttpResponseRedirect(new_obj.get_absolute_url()) 96 else: 97 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 98 99 class DeleteView(BaseDetailView): 100 def __init__(self, queryset, slug_field='slug', post_save_redirect=None): 101 self.post_save_redirect = post_save_redirect 102 super(DeleteView, self).__init__(queryset, slug_field) 103 104 def __call__(self, request, object_pk=None, slug=None): 105 obj = self.get_object(request, object_pk, slug) 106 if request.method == 'POST': 107 self.delete(obj) 108 return self.on_success(request, obj) 109 context_vars = {'object': obj} 110 template = self.get_template(request, obj) 111 response = self.render_response(request, template, context_vars) 112 populate_xheaders(request, response, self.model, obj.pk) 113 return response 114 115 def get_template(self, request, obj=None): 116 opts = self.model._meta 117 template_name = "%s/%s_confirm_delete.html" % (opts.app_label, opts.object_name.lower()) 118 return loader.get_template(template_name) 119 120 def delete(request, obj): 121 """ 122 Deletes the given instance. Subclasses that wish to veto deletion 123 should do so here. 124 """ 125 obj.delete() 126 127 def on_success(self, request, obj): 128 """ 129 Redirects to self.post_save_redirect after setting a message if the 130 user is logged in. 131 132 This method is only called if saving the object was successful. 133 """ 134 if request.user.is_authenticated(): 135 message = self.get_message(request, obj) 136 request.user.message_set.create(message=message) 137 return HttpResponseRedirect(self.post_save_redirect) 138 139 def get_message(self, request, new_object): 140 return ugettext("The %s was deleted.") % self.model._meta.verbose_name 141 142 143 # Classic generic views ###################################################### 10 144 11 145 def create_object(request, model, template_name=None, 12 146 template_loader=loader, extra_context=None, post_save_redirect=None, -
django/views/generic/list_detail.py
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py index cb9b014..2029a8a 100644
a b from django.http import Http404, HttpResponse 3 3 from django.core.xheaders import populate_xheaders 4 4 from django.core.paginator import QuerySetPaginator, InvalidPage 5 5 from django.core.exceptions import ObjectDoesNotExist 6 from django.views.generic.base import BaseView, BaseDetailView 7 8 class ObjectList(BaseView): 9 """ 10 Generic list of objects. 11 12 Templates: ``<app_label>/<model_name>_list.html`` 13 Context: 14 object_list 15 list of objects 16 paginator 17 a ``QuerySetPaginator``object if pagination is enabled, None otherwise 18 page_obj 19 a ``Page`` object if pagination is enabled, None otherwise 20 """ 21 def __init__(self, queryset, paginate_by=None, allow_empty=True): 22 self.paginate_by = paginate_by 23 self.allow_empty = allow_empty 24 super(ObjectList, self).__init__(queryset) 25 26 def __call__(self, request, page=None): 27 queryset = self.get_query_set(request) 28 if self.paginate_by: 29 paginator = QuerySetPaginator(queryset, self.paginate_by, 30 allow_empty_first_page=self.allow_empty) 31 if not page: 32 page = request.GET.get('page', 1) 33 try: 34 page_number = int(page) 35 except ValueError: 36 if page == 'last': 37 page_number = paginator.num_pages 38 else: 39 # Page is not 'last', nor can it be converted to an int. 40 raise Http404 41 try: 42 page_obj = paginator.page(page_number) 43 except InvalidPage: 44 raise Http404 45 object_list = page_obj.object_list 46 else: 47 object_list = queryset 48 paginator = None 49 page_obj = None 50 if not self.allow_empty and len(queryset) == 0: 51 raise Http404 52 context_vars = { 53 'object_list': object_list, 54 'paginator': paginator, 55 'page_obj': page_obj 56 } 57 template = self.get_template(request) 58 return self.render_response(request, template, context_vars) 59 60 def get_template(self, request, obj=None): 61 """ 62 Returns the template to be used when rendering this view. Those who 63 wish to use a custom template loader should do so here. 64 """ 65 opts = self.queryset.model._meta 66 template_name = "%s/%s_list.html" % (opts.app_label, opts.object_name.lower()) 67 return loader.get_template(template_name) 68 69 class ObjectDetail(BaseDetailView): 70 """ 71 Generic detail of an object. 72 73 Templates: ``<app_label>/<model_name>_detail.html`` 74 Context: 75 object 76 the object 77 """ 78 def __init__(self, queryset, slug_field='slug'): 79 super(ObjectDetail, self).__init__(queryset, slug_field) 80 81 def __call__(self, request, object_pk=None, slug=None): 82 queryset = self.get_query_set(request) 83 opts = queryset.model._meta 84 try: 85 obj = self.get_object(request, object_pk, slug) 86 except ObjectDoesNotExist: 87 raise Http404(u"No %s found matching the query" % opts.verbose_name) 88 context_vars = {'object': obj} 89 template = self.get_template(request, obj) 90 response = self.render_response(request, template, context_vars) 91 populate_xheaders(request, response, queryset.model, getattr(obj, opts.pk.name)) 92 return response 93 94 def get_template(self, request, obj=None): 95 """ 96 Returns the template to be used when rendering this view. Those who 97 wish to use a custom template loader should do so here. 98 """ 99 opts = self.queryset.model._meta 100 template_name = "%s/%s_detail.html" % (opts.app_label, opts.object_name.lower()) 101 return loader.get_template(template_name) 102 103 # Legacy generic views ####################################################### 6 104 7 105 def object_list(request, queryset, paginate_by=None, page=None, 8 106 allow_empty=True, template_name=None, template_loader=loader, -
tests/regressiontests/views/models.py
diff --git a/tests/regressiontests/views/models.py b/tests/regressiontests/views/models.py index 4bed1f3..472ac2b 100644
a b class Article(models.Model): 20 20 slug = models.SlugField() 21 21 author = models.ForeignKey(Author) 22 22 date_created = models.DateTimeField() 23 23 24 24 def __unicode__(self): 25 25 return self.title 26 -
tests/regressiontests/views/tests/__init__.py
diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py index 2c8c5b4..3def6a9 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.list_detail import * 5 from generic.date_based import * 6 from generic.create_update import * -
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..7c59eb4
- + 1 # coding: utf-8 2 from django.test import TestCase 3 from regressiontests.views.models import Article, Author 4 5 class AddViewTest(TestCase): 6 fixtures = ['testdata.json'] 7 8 def test_initial(self): 9 response = self.client.get('/views/create_update/article/add/') 10 self.assertEqual(response.status_code, 200) 11 self.assertEqual(response.context['form']._meta.model, Article) 12 self.assertEqual(response.context['object'], None) 13 14 def test_submit(self): 15 response = self.client.post('/views/create_update/article/add/', { 16 'author': '1', 17 'title': "Don't read this", 18 'slug': 'dont-read-this', 19 'date_created': '2001-01-01 21:22:23' 20 }) 21 self.assertEqual(response.status_code, 302) 22 23 class ChangeViewByIdTest(TestCase): 24 fixtures = ['testdata.json'] 25 26 def test_initial(self): 27 response = self.client.get('/views/create_update/article/1/change/') 28 self.assertEqual(response.status_code, 200) 29 self.assertEqual(response.context['form']._meta.model, Article) 30 self.assertEqual(response.context['object'].title, u'Old Article') 31 32 def test_submit(self): 33 response = self.client.post('/views/create_update/article/1/change/', { 34 'author': '1', 35 'title': 'Ta Da!', 36 'slug': 'ta-da', 37 'date_created': '2001-01-01 21:22:23' 38 }) 39 self.assertEqual(response.status_code, 302) 40 41 class ChangeViewBySlugTest(TestCase): 42 fixtures = ['testdata.json'] 43 44 def test_initial(self): 45 response = self.client.get('/views/create_update/article/old_article/change/') 46 self.assertEqual(response.status_code, 200) 47 self.assertEqual(response.context['form']._meta.model, Article) 48 self.assertEqual(response.context['object'].title, u'Old Article') 49 50 def test_submit(self): 51 response = self.client.post('/views/create_update/article/old_article/change/', { 52 'author': '1', 53 'title': 'Ta Da!', 54 'slug': 'ta-da', 55 'date_created': '2001-01-01 21:22:23' 56 }) 57 self.assertEqual(response.status_code, 302) 58 59 class DeleteViewByIdTest(TestCase): 60 fixtures = ['testdata.json'] 61 62 def test_initial(self): 63 response = self.client.get('/views/create_update/article/1/delete/') 64 self.assertEqual(response.status_code, 200) 65 self.assertEqual(response.context['object'].title, u'Old Article') 66 67 def test_submit(self): 68 response = self.client.post('/views/create_update/article/1/delete/', {}) 69 self.assertEqual(response.status_code, 302) 70 71 class DeleteViewBySlugTest(TestCase): 72 fixtures = ['testdata.json'] 73 74 def test_initial(self): 75 response = self.client.get('/views/create_update/article/old_article/delete/') 76 self.assertEqual(response.status_code, 200) 77 self.assertEqual(response.context['object'].title, u'Old Article') 78 79 def test_submit(self): 80 response = self.client.post('/views/create_update/article/old_article/delete/', {}) 81 self.assertEqual(response.status_code, 302) -
new file tests/regressiontests/views/tests/generic/list_detail.py
diff --git a/tests/regressiontests/views/tests/generic/list_detail.py b/tests/regressiontests/views/tests/generic/list_detail.py new file mode 100644 index 0000000..de9d50e
- + 1 # coding: utf-8 2 from django.test import TestCase 3 from regressiontests.views.models import Article, Author 4 5 class ObjectListViewTest(TestCase): 6 fixtures = ['testdata.json'] 7 8 def test_basic(self): 9 response = self.client.get('/views/articles/') 10 self.assertEqual(response.status_code, 200) 11 self.assertEqual(response.template.name, 'views/article_list.html') 12 self.assertEqual(len(response.context['object_list']), 3) 13 self.assertEqual(response.context['paginator'], None) 14 self.assertEqual(response.context['page_obj'], None) 15 16 class ObjectDetailViewTest(TestCase): 17 fixtures = ['testdata.json'] 18 19 def test_by_id(self): 20 response = self.client.get('/views/articles/1/') 21 self.assertEqual(response.status_code, 200) 22 self.assertEqual(response.template.name, 'views/article_detail.html') 23 self.assertEqual(response.context['object'].title, u'Old Article') 24 25 def test_by_slug(self): 26 response = self.client.get('/views/articles/old_article/') 27 self.assertEqual(response.status_code, 200) 28 self.assertEqual(response.template.name, 'views/article_detail.html') 29 self.assertEqual(response.context['object'].title, u'Old Article') -
tests/regressiontests/views/urls.py
diff --git a/tests/regressiontests/views/urls.py b/tests/regressiontests/views/urls.py index 5ef0c51..5c3325e 100644
a b 1 1 from os import path 2 2 3 3 from django.conf.urls.defaults import * 4 from django.views.generic.list_detail import ObjectList, ObjectDetail 5 from django.views.generic.create_update import EditView, DeleteView 4 6 5 7 from models import * 6 8 import views … … base_dir = path.dirname(path.abspath(__file__)) 9 11 media_dir = path.join(base_dir, 'media') 10 12 locale_dir = path.join(base_dir, 'locale') 11 13 14 # List/Detail views 15 article_list = ObjectList(Article.objects.all()) 16 article_detail = ObjectDetail(Article.objects.all()) 17 18 # Create/Update/Delete Views 19 article_add = article_change = EditView(Article.objects.all(), post_save_redirect='../') 20 article_delete = DeleteView(Article.objects.all()) 21 22 12 23 js_info_dict = { 13 24 'domain': 'djangojs', 14 25 'packages': ('regressiontests.views',), … … urlpatterns = patterns('', 34 45 35 46 # Static views 36 47 (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}), 37 38 # Date-based generic views 48 49 # Create/Update generic views 50 (r'create_update/article/add/$', article_add), 51 (r'create_update/article/(?P<object_pk>\d+)/change/$', article_change), 52 (r'create_update/article/(?P<slug>\w+)/change/$', article_change), 53 (r'create_update/article/(?P<object_pk>\d+)/delete/$', article_delete), 54 (r'create_update/article/(?P<slug>\w+)/delete/$', article_delete), 55 56 (r'articles/$', article_list), 57 (r'articles/(?P<object_pk>\d+)/$', article_detail), 58 (r'articles/(?P<slug>\w+)/$', article_detail), 59 60 61 # Date-based generic views 39 62 (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$', 40 63 'django.views.generic.date_based.object_detail', 41 64 dict(slug_field='slug', **date_based_info_dict)), -
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 -
new file tests/templates/views/article_list.html
diff --git a/tests/templates/views/article_list.html b/tests/templates/views/article_list.html new file mode 100644 index 0000000..3f8ff55
- + 1 This template intentionally left blank 2 No newline at end of file