Ticket #6735: new-generic-views.diff

File new-generic-views.diff, 11.8 KB (added by jkocherhans, 16 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..ab8b9fb 100644
    a b from django.template import RequestContext  
    77from django.http import Http404, HttpResponse, HttpResponseRedirect
    88from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
    99from django.utils.translation import ugettext
     10from django import newforms as forms
     11from django.newforms.models import ModelFormMetaclass, ModelForm
     12
     13
     14# New-sytle, class based generic views #######################################
     15
     16class BaseView(object):
     17    """
     18    Base class for generic object creation and update view.
     19
     20    Templates: ``<app_label>/<model_name>_form.html``
     21    Context:
     22        form
     23            the ``ModelForm`` instance for the object
     24    """
     25    def __init__(self, model, post_save_redirect=None):
     26        self.model = model
     27        self.post_save_redirect = None
     28
     29    def __call__(self, request):
     30        return self.main(request, self.get_instance(request))
     31
     32    def main(self, request, instance):
     33        Form = self.get_form(request)
     34        if request.POST:
     35            form = Form(request.POST, request.FILES, instance=instance)
     36            if form.is_valid():
     37                new_object = self.save(request, form)
     38                return self.post_save(request, new_object)
     39        else:
     40            form = Form()
     41
     42        t = self.get_template(request)
     43        c = self.get_context(request, form, instance)
     44        return HttpResponse(t.render(c))
     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):
     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_context(self, request, form, instance):
     67        """
     68        Returns a ``Context`` instance to be used when rendering this view.
     69        """
     70        return RequestContext(request, {'form': form, 'object': instance})
     71
     72    def save(self, request, form):
     73        """
     74        Saves the object represented by the given ``form``. This method will
     75        only be called if the form is valid, and should in most cases return
     76        an HttpResponseRediect. It's return value will be the return value
     77        for the view on success.
     78        """
     79        return form.save()
     80
     81    def post_save(self, request, new_object):
     82        """
     83        Returns
     84        """
     85        if request.user.is_authenticated():
     86            message = self.get_message(request, new_object)
     87            request.user.message_set.create(message=message)
     88        # Redirect to the new object: first by trying post_save_redirect,
     89        # then by obj.get_absolute_url; fail if neither works.
     90        if self.post_save_redirect:
     91            return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
     92        elif hasattr(new_object, 'get_absolute_url'):
     93            return HttpResponseRedirect(new_object.get_absolute_url())
     94        else:
     95            raise ImproperlyConfigured("No URL to redirect to from generic create view.")
     96
     97class AddView(BaseView):
     98    def get_instance(self, request):
     99        """
     100        Returns the object instance to create.
     101        """
     102        return self.model()
     103
     104    def get_message(self, request, new_object):
     105        return ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": self.model._meta.verbose_name}
     106
     107class ChangeView(BaseView):
     108    def __init__(self, model, post_save_redirect=None, slug_field='slug'):
     109        self.slug_field = slug_field
     110        super(ChangeView, self).__init__(model, post_save_redirect=post_save_redirect)
     111
     112    def __call__(self, request, object_id=None, slug=None):
     113        return self.main(request, self.get_instance(request, object_id, slug))
     114
     115    def get_query_set(self, request):
     116        """
     117        Returns a queryset to use when trying to look up the object to change.
     118        """
     119        return self.model._default_manager.get_query_set()
     120
     121    def get_instance(self, request, object_id=None, slug=None):
     122        """
     123        Returns the object to be changed, or raises a 404 if it doesn't exist.
     124        """
     125        # Look up the object to be edited
     126        lookup_kwargs = {}
     127        if object_id:
     128            lookup_kwargs['pk'] = object_id
     129        elif slug and self.slug_field:
     130            lookup_kwargs['%s__exact' % slug_field] = slug
     131        else:
     132            raise AttributeError("Generic view must be called with either an object_id or a slug/slug_field")
     133        try:
     134            return self.get_query_set(request).get(**lookup_kwargs)
     135        except ObjectDoesNotExist:
     136            raise Http404, "No %s found for %s" % (self.model._meta.verbose_name, lookup_kwargs)
     137
     138    def get_message(self, request, new_object):
     139        return ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": self.model._meta.verbose_name}
     140
     141class DeleteView(ChangeView):
     142    def __init__(self, model, post_save_redirect=None, slug_field='slug'):
     143        self.model = model
     144        self.slug_field = slug_field
     145        self.post_save_redirect = post_save_redirect
     146
     147    def main(self, request, instance):
     148        if request.method == 'POST':
     149            self.delete(instance)
     150            return self.post_save(request, instance)
     151        t = self.get_template(request)
     152        c = self.get_context(request, instance)
     153        response = HttpResponse(t.render(c))
     154        populate_xheaders(request, response, self.model, instance.pk)
     155        return response
     156
     157    def get_context(self, request, instance):
     158        """
     159        Returns a ``Context`` instance to be used when rendering this view.
     160        """
     161        return RequestContext(request, {'object': instance})
     162
     163    def get_template(self, request):
     164        opts = self.model._meta
     165        template_name = "%s/%s_confirm_delete.html" % (opts.app_label, opts.object_name.lower())
     166        return loader.get_template(template_name)
     167
     168    def delete(request, instance):
     169        """
     170        Deletes the given instance. Subclasses that wish to veto deletion
     171        should do so here.
     172        """
     173        instance.delete()
     174
     175    def post_save(self, request, new_object):
     176        """
     177        Redirects to self.post_save_redirect after setting a message if the
     178        user is logged in.
     179       
     180        This method is only called if saving the object was successful.
     181        """
     182        if request.user.is_authenticated():
     183            message = self.get_message(request, new_object)
     184            request.user.message_set.create(message=message)
     185        return HttpResponseRedirect(self.post_save_redirect)
     186
     187    def get_message(self, request, new_object):
     188        return ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}
     189
     190
     191# Classic generic views ######################################################
    10192
    11193def create_object(request, model, template_name=None,
    12194        template_loader=loader, extra_context=None, post_save_redirect=None,
  • tests/regressiontests/views/tests/__init__.py

    diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py
    index 2c8c5b4..f0f6f59 100644
    a b  
    11from defaults import *
    22from i18n import *
    33from static import *
     4from generic.create_update import *
    45from generic.date_based 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..7cdbb9e
    - +  
     1# coding: utf-8
     2from django.test import TestCase
     3from regressiontests.views.models import Article, Author
     4
     5class AddViewTest(TestCase):
     6
     7    def test_initial(self):
     8        response = self.client.get('/views/create_update/add/')
     9        self.assertEqual(response.status_code, 200)
     10        self.assertEqual(response.context['form']._meta.model, Author)
     11        self.assertEqual(response.context['object'].name, "")
     12
     13    def test_submit(self):
     14        response = self.client.post('/views/create_update/add/', {
     15            'name': 'Boris',
     16        })
     17        self.assertEqual(response.status_code, 302)
     18
     19class ChangeViewTest(TestCase):
     20    fixtures = ['testdata.json']
     21
     22    def test_initial(self):
     23        response = self.client.get('/views/create_update/1/change/')
     24        self.assertEqual(response.status_code, 200)
     25        self.assertEqual(response.context['form']._meta.model, Author)
     26        self.assertEqual(response.context['object'].name, "Boris")
     27
     28    def test_submit(self):
     29        response = self.client.post('/views/create_update/1/change/', {
     30            'name': 'Jack Kerouac',
     31        })
     32        self.assertEqual(response.status_code, 302)
     33
     34class DeleteViewTest(TestCase):
     35    fixtures = ['testdata.json']
     36
     37    def test_initial(self):
     38        response = self.client.get('/views/create_update/1/delete/')
     39        self.assertEqual(response.status_code, 200)
     40        self.assertEqual(response.context['object'].name, "Boris")
     41
     42    def test_submit(self):
     43        response = self.client.post('/views/create_update/1/delete/', {})
     44        self.assertEqual(response.status_code, 302)
  • tests/regressiontests/views/urls.py

    diff --git a/tests/regressiontests/views/urls.py b/tests/regressiontests/views/urls.py
    index 5ef0c51..ebd5721 100644
    a b  
    11from os import path
    22
    33from django.conf.urls.defaults import *
     4from django.views.generic.create_update import AddView, ChangeView, DeleteView
    45
    56from models import *
    67import views
    base_dir = path.dirname(path.abspath(__file__))  
    910media_dir = path.join(base_dir, 'media')
    1011locale_dir = path.join(base_dir, 'locale')
    1112
     13author_add = AddView(Author)
     14author_change = ChangeView(Author)
     15author_delete = DeleteView(Author)
     16
    1217js_info_dict = {
    1318    'domain': 'djangojs',
    1419    'packages': ('regressiontests.views',),
    urlpatterns = patterns('',  
    3439   
    3540    # Static views
    3641    (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
    37    
    38         # Date-based generic views
     42
     43    # Create/Update generic views
     44    (r'create_update/add/', author_add),
     45    (r'create_update/(?P<object_id>\d+)/change/', author_change),
     46    (r'create_update/(?P<object_id>\d+)/delete/', author_delete),
     47
     48
     49    # Date-based generic views
    3950    (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
    4051        'django.views.generic.date_based.object_detail',
    4152        dict(slug_field='slug', **date_based_info_dict)),
  • new file tests/templates/views/author_confirm_delete.html

    diff --git a/tests/templates/views/author_confirm_delete.html b/tests/templates/views/author_confirm_delete.html
    new file mode 100644
    index 0000000..3f8ff55
    - +  
     1This template intentionally left blank
     2 No newline at end of file
  • new file tests/templates/views/author_form.html

    diff --git a/tests/templates/views/author_form.html b/tests/templates/views/author_form.html
    new file mode 100644
    index 0000000..3f8ff55
    - +  
     1This template intentionally left blank
     2 No newline at end of file
Back to Top