Ticket #6735: generic-views.diff
File generic-views.diff, 21.6 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..69880c2
- + 1 from django.db.models.base import ModelBase 2 from django.db.models.query import _QuerySet 3 from django.http import Http404, HttpResponse 4 from django.core.exceptions import ObjectDoesNotExist 5 from django.template import RequestContext 6 7 class BaseView(object): 8 """ 9 Base class for creating class based view objects. 10 """ 11 def __init__(self, queryset): 12 self.queryset = queryset 13 self.model = queryset.model 14 15 def get_query_set(self, request): 16 """ 17 Hook to provide a custom queryset based on the request. 18 """ 19 return self.queryset._clone() 20 21 def get_template(self, request, obj=None): 22 """ 23 Returns a template object used to render this view. 24 """ 25 raise NotImplementedError(u'Views must implement their own get_template method.') 26 27 def render_response(self, request, template, context_vars, mimetype=None): 28 """ 29 Returns an HttpResponse for the given request, template object, 30 dictionary of context variables, and optional mimetype. 31 """ 32 context = RequestContext(request, context_vars) 33 template = template.render(context) 34 return HttpResponse(template, mimetype=mimetype) 35 36 class BaseDetailView(BaseView): 37 def __init__(self, queryset, slug_field='slug'): 38 self.slug_field = slug_field 39 super(BaseDetailView, self).__init__(queryset) 40 41 def get_object(self, request, object_pk=None, slug=None): 42 """ 43 Returns the object to be viewed, changed or deleted, or raises a 404 44 if it doesn't exist. 45 """ 46 # Look up the object to be changed or deleted 47 lookup_kwargs = {} 48 if object_pk: 49 lookup_kwargs['pk'] = object_pk 50 elif slug and self.slug_field: 51 lookup_kwargs[self.slug_field] = slug 52 else: 53 raise AttributeError("Generic view must be called with either an object_pk or a slug/slug_field") 54 try: 55 return self.get_query_set(request).get(**lookup_kwargs) 56 except ObjectDoesNotExist: 57 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..774792d 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 original = None 32 else: 33 original = 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=original) 37 if form.is_valid(): 38 new_object = self.save_form(request, form) 39 return self.on_success(request, original, new_object) 40 else: 41 form = Form() 42 context_vars = {'object': original, 'form': form} 43 template = self.get_template(request, original) 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, original, new_object): 67 # If the primary ke of the original object is None, we just created an 68 # object, otherwise, we updated one. 69 if original.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 the object represented by the given ``form``. This method will 76 only be called if the form is valid, and should in most cases return 77 an HttpResponseRediect. It's return value will be the return value 78 for the view on success. 79 """ 80 return form.save() 81 82 def on_success(self, request, original, new_object): 83 """ 84 Returns an HttpResonse, generally an HttpResponse redirect. This will 85 be the final return value of the view and will only be called if the 86 object was saved successfuly. 87 """ 88 # We can only do messaging for authenticated users right now 89 if request.user.is_authenticated(): 90 message = self.get_message(request, new_object) 91 request.user.message_set.create(message=message) 92 # Redirect to the new object: first by trying post_save_redirect, 93 # then by obj.get_absolute_url; fail if neither works. 94 if self.post_save_redirect: 95 return HttpResponseRedirect(self.post_save_redirect % new_object.__dict__) 96 elif hasattr(new_object, 'get_absolute_url'): 97 return HttpResponseRedirect(new_object.get_absolute_url()) 98 else: 99 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 100 101 class DeleteView(BaseDetailView): 102 def __init__(self, queryset, slug_field='slug', post_save_redirect=None): 103 self.post_save_redirect = post_save_redirect 104 super(DeleteView, self).__init__(queryset, slug_field) 105 106 def __call__(self, request, object_pk=None, slug=None): 107 original = self.get_object(request, object_pk, slug) 108 if request.method == 'POST': 109 self.delete(original) 110 return self.on_success(request, original) 111 context_vars = {'object': original} 112 template = self.get_template(request, original) 113 response = self.render_response(request, template, context_vars) 114 populate_xheaders(request, response, self.model, original.pk) 115 return response 116 117 def get_template(self, request, obj=None): 118 opts = self.model._meta 119 template_name = "%s/%s_confirm_delete.html" % (opts.app_label, opts.object_name.lower()) 120 return loader.get_template(template_name) 121 122 def delete(request, obj): 123 """ 124 Deletes the given instance. Subclasses that wish to veto deletion 125 should do so here. 126 """ 127 obj.delete() 128 129 def on_success(self, request, new_object): 130 """ 131 Redirects to self.post_save_redirect after setting a message if the 132 user is logged in. 133 134 This method is only called if saving the object was successful. 135 """ 136 if request.user.is_authenticated(): 137 message = self.get_message(request, new_object) 138 request.user.message_set.create(message=message) 139 return HttpResponseRedirect(self.post_save_redirect) 140 141 def get_message(self, request, new_object): 142 return ugettext("The %s was deleted.") % self.model._meta.verbose_name 143 144 145 # Classic generic views ###################################################### 10 146 11 147 def create_object(request, model, template_name=None, 12 148 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..9286b55 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): 27 queryset = self.get_query_set(request) 28 if self.paginate_by: 29 paginator = QuerySetPaginator(queryset, 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