Ticket #6735: new-generic-views.3.diff

File new-generic-views.3.diff, 12.4 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..e9a2dc9 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.on_success(request, new_object)
     39        else:
     40            form = Form()
     41        rendered_template = self.get_rendered_template(request, instance, form)
     42        return HttpResponse(rendered_template)
     43
     44    def get_form(self, request):
     45        """
     46        Returns a ``ModelForm`` class to be used in this view.
     47        """
     48        # TODO: we should be able to construct a ModelForm without creating
     49        # and passing in a temporary inner class
     50        class Meta:
     51            model = self.model
     52        class_name = self.model.__name__ + 'Form'
     53        return ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta})
     54
     55    def get_context(self, request, instance, form=None):
     56        """
     57        Returns a ``Context`` instance to be used when rendering this view.
     58        """
     59        return RequestContext(request, {'form': form, 'object': instance})
     60
     61    def get_template(self, request):
     62        """
     63        Returns the template to be used when rendering this view. Those who
     64        wish to use a custom template loader should do so here.
     65        """
     66        opts = self.model._meta
     67        template_name = "%s/%s_form.html" % (opts.app_label, opts.object_name.lower())
     68        return loader.get_template(template_name)
     69
     70    def get_rendered_template(self, request, instance, form=None):
     71        """
     72        Returns a rendered template. This will be passed as the sole argument
     73        to HttpResponse()
     74        """
     75        template = self.get_template(request)
     76        context = self.get_context(request, instance, form)
     77        return template.render(context)
     78
     79    def save(self, request, form):
     80        """
     81        Saves the object represented by the given ``form``. This method will
     82        only be called if the form is valid, and should in most cases return
     83        an HttpResponseRediect. It's return value will be the return value
     84        for the view on success.
     85        """
     86        return form.save()
     87
     88    def on_success(self, request, new_object):
     89        """
     90        Returns an HttpResonse, generally an HttpResponse redirect. This will
     91        be the final return value of the view and will only be called if the
     92        object was saved successfuly.
     93        """
     94        if request.user.is_authenticated():
     95            message = self.get_message(request, new_object)
     96            request.user.message_set.create(message=message)
     97        # Redirect to the new object: first by trying post_save_redirect,
     98        # then by obj.get_absolute_url; fail if neither works.
     99        if self.post_save_redirect:
     100            return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
     101        elif hasattr(new_object, 'get_absolute_url'):
     102            return HttpResponseRedirect(new_object.get_absolute_url())
     103        else:
     104            raise ImproperlyConfigured("No URL to redirect to from generic create view.")
     105
     106class AddView(BaseView):
     107    def get_instance(self, request):
     108        """
     109        Returns the object instance to create.
     110        """
     111        return self.model()
     112
     113    def get_message(self, request, new_object):
     114        return ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": self.model._meta.verbose_name}
     115
     116class ChangeView(BaseView):
     117    def __init__(self, model, post_save_redirect=None, slug_field='slug'):
     118        self.slug_field = slug_field
     119        super(ChangeView, self).__init__(model, post_save_redirect=post_save_redirect)
     120
     121    def __call__(self, request, object_id=None, slug=None):
     122        return self.main(request, self.get_instance(request, object_id, slug))
     123
     124    def get_query_set(self, request):
     125        """
     126        Returns a queryset to use when trying to look up the object to change.
     127        """
     128        return self.model._default_manager.get_query_set()
     129
     130    def get_instance(self, request, object_id=None, slug=None):
     131        """
     132        Returns the object to be changed, or raises a 404 if it doesn't exist.
     133        """
     134        # Look up the object to be edited
     135        lookup_kwargs = {}
     136        if object_id:
     137            lookup_kwargs['pk'] = object_id
     138        elif slug and self.slug_field:
     139            lookup_kwargs['%s__exact' % slug_field] = slug
     140        else:
     141            raise AttributeError("Generic view must be called with either an object_id or a slug/slug_field")
     142        try:
     143            return self.get_query_set(request).get(**lookup_kwargs)
     144        except ObjectDoesNotExist:
     145            raise Http404, "No %s found for %s" % (self.model._meta.verbose_name, lookup_kwargs)
     146
     147    def get_message(self, request, new_object):
     148        return ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": self.model._meta.verbose_name}
     149
     150class DeleteView(ChangeView):
     151    def __init__(self, model, post_save_redirect=None, slug_field='slug'):
     152        self.model = model
     153        self.slug_field = slug_field
     154        self.post_save_redirect = post_save_redirect
     155
     156    def main(self, request, instance):
     157        if request.method == 'POST':
     158            self.delete(instance)
     159            return self.on_success(request, instance)
     160        rendered_template = self.get_rendered_template(request, instance)
     161        response = HttpResponse(rendered_template)
     162        populate_xheaders(request, response, self.model, instance.pk)
     163        return response
     164
     165    def get_context(self, request, instance, form=None):
     166        """
     167        Returns a ``Context`` instance to be used when rendering this view.
     168        """
     169        return RequestContext(request, {'object': instance})
     170
     171    def get_template(self, request):
     172        opts = self.model._meta
     173        template_name = "%s/%s_confirm_delete.html" % (opts.app_label, opts.object_name.lower())
     174        return loader.get_template(template_name)
     175
     176    def delete(request, instance):
     177        """
     178        Deletes the given instance. Subclasses that wish to veto deletion
     179        should do so here.
     180        """
     181        instance.delete()
     182
     183    def on_success(self, request, new_object):
     184        """
     185        Redirects to self.post_save_redirect after setting a message if the
     186        user is logged in.
     187       
     188        This method is only called if saving the object was successful.
     189        """
     190        if request.user.is_authenticated():
     191            message = self.get_message(request, new_object)
     192            request.user.message_set.create(message=message)
     193        return HttpResponseRedirect(self.post_save_redirect)
     194
     195    def get_message(self, request, new_object):
     196        return ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}
     197
     198
     199# Classic generic views ######################################################
    10200
    11201def create_object(request, model, template_name=None,
    12202        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