Class Based Views: CreateView Example

For this example I’m not starting with a generic view. That’s because the code includes support for an extra submit button – in this case “Save and Add Another”. Without it, I would have used the generic view.

Before:


@permission_required('calendars.add_eventtype')
def create_type(request):
    evttypeform = EventTypeForm(request.POST or None)
    
    if evttypeform.is_valid():
        evttypeform.save()
        if '_addanother' in request.POST:
            return HttpResponseRedirect(reverse('create_type'))
    
        return HttpResponseRedirect(reverse('list_type'))
            
    return render_to_response('eventtype_form.html', 
                              {'form' : evttypeform, 'create_form': True,
                    'calendar_menu': CalendarName.menu.all(),   },
                               context_instance=RequestContext(request))

Perhaps the Generic View would have looked something like this (not tested):

@permission_required('calendars.add_eventtype')
def create_type(request):
    return create_object(request,
                         form_class = EventTypeForm,
                         post_save_redirect=reverse('list_type'),
                         template_name='eventtype_form.html',
                         extra_context={'create_form': True,
                                        'calendar_menu': CalendarName.menu.all(),})    

After:

class TypeCreateView(CreateView):
    template_name = 'eventtype_form.html'
    model = EventType
    form_class = EventTypeForm
    
    def get_success_url(self):
        if '_addanother' in self.request.POST:
            return reverse('create_type')
        return reverse('list_type')
    
    ## Override dispatch to apply the permission decorator
    @method_decorator(permission_required('calendars.add_eventtype'))
    def dispatch(self, request, *args, **kwargs):
        return super(TypeCreateView, self).dispatch(request, *args, **kwargs)
    
    ## Additional context
    def get_context_data(self, **kwargs):
        context = super(TypeCreateView, self).get_context_data(**kwargs)
        context['calendar_menu'] = CalendarName.menu.all()
        context['create_form'] = True
        return context 

One of the benefits of the class based views is to be able to override individual methods. Here the get_success_url method can handle the extra functionality of the “Add Another” button.

Class Based Views – UpdateView Example

Yet another simple example. I basically just took the DeleteView example and changed a couple of things.

Before:

@permission_required('calendars.change_eventtype')
def update_type(request,id):
    return update_object(request,
                         model=EventType,
                         object_id=id,
                         template_name='eventtype_form.html',
                         post_save_redirect=reverse('list_type'),
                       extra_context = {'calendar_menu': CalendarName.menu.all()})

After:

class TypeUpdateView(UpdateView):
    template_name = 'eventtype_form.html'
    model = EventType
    
    def get_success_url(self):
        return reverse('list_type')
    
    ## Override dispatch to apply the permission decorator
    @method_decorator(permission_required('calendars.change_eventtype'))
    def dispatch(self, request, *args, **kwargs):
        return super(TypeUpdateView, self).dispatch(request, *args, **kwargs)
    
    ## Additional context
    def get_context_data(self, **kwargs):
        context = super(TypeUpdateView, self).get_context_data(**kwargs)
        context['calendar_menu'] = CalendarName.menu.all()
        return context 

With another example, the generic view declared a form_class instead of a model. The model declaration is not necessary since the related model is tied to the form in its meta section. However, in the class based generic view, both were required (or a queryset could have also been used). I guess the related model info doesn’t make its way back to the class. Hmmmm.

Class Based Views – DeleteView Example

Another simple one – this time a DeleteView from delete_object.

Before:

@permission_required('b2c.delete_b2ctrack')
def delete_track(request,id):
    track = get_object_or_404(B2CTrack, pk=id)
    if track.allow_delete:
        return delete_object(request,
                             model=B2CTrack,
                             object_id=id,
                             post_delete_redirect=reverse('track_list'),
                             template_name='track_confirm_delete.html')
    else:
        return HttpResponseRedirect(reverse('track_view', kwargs = {'pk':id} ))

After:

class TrackDeleteView(DeleteView):
    template_name = 'track_confirm_delete.html'
    model = B2CTrack
    
    def get_success_url(self):
        return reverse('track_list')
    
    ## Override dispatch to apply the permission decorator
    @method_decorator(permission_required('b2c.delete_b2ctrack'))
    def dispatch(self, request, *args, **kwargs):
        return super(TrackDeleteView, self).dispatch(request, *args, **kwargs)
    
    ## Only return object if the allow_delete property is True
    def get_object(self, *args, **kwargs):
        object = super(TrackDeleteView,self).get_object(*args, **kwargs)
        if object.allow_delete:
            return object
        else:
            raise Http404

The old code would redirect back to the view for the same object if the allow_delete property was false. (allow_delete is a property I use on my models to check for dependent DB entries) Since one has to monkey with the URL to even attempt a delete of an object that isn’t allowed, I decided a 404 was more appropriate. Lesson for the User: Don’t Monkey with URLs!!

Class Based Views – DetailView Example

This is a simple example that shows how to convert the object_detail generic view to the class based DetailView.

Before:

@permission_required('b2c.view_b2ctrack')
def view_track(request,id):
    return object_detail(request,
                         queryset=B2CTrack.objects.all(),
                         object_id=id,
                         template_name='track_detail.html')

After:

class TrackDetailView(DetailView):
    template_name = 'track_detail.html'
    model = B2CTrack
    
    ## Override dispatch to apply the permission decorator
    @method_decorator(permission_required('b2c.view_b2ctrack'))
    def dispatch(self, *args, **kwargs):
        return super(TrackDetailView, self).dispatch(*args, **kwargs)  

Only odd thing I encountered was that the new View wants to see the ID of the object named ‘pk’, instead of ‘id’ that I was using. The DEV branch of Django includes the pk_url_kwarg variable that allows a rename, but it isn’t available in v1.3.

Note that I could have used queryset instead of model to specify the data source. Either will give the same result in this example.

More on Class Based Views – Redirect If Data Missing

In this view, rather than raising a 404 if a piece of data is missing, I redirect back to a page where the user can specify the value.

def list_session(request):
    event_id = request.session.get('event_id',None)
    try:
        event = Event.objects.get(pk=event_id)
    except:  #No event - back to staff page to select
        return HttpResponseRedirect(reverse('staff'))

    queryset = B2CSession.objects.filter(event = event)

    params = {'queryset': queryset,
              'paginate_by': DEFAULT_PAGINATION,
              'template_name': 'session_list.html',
              'extra_context': {'event': event,
                                'session': session}}
    return object_list(request, **params)

I wanted to implement this same functionality using a class based view, so I looked into the available methods to override. The dispatch method was really the only choice. My solution:

def dispatch(self, request, *args, **kwargs):
        ## If there is no event_id set in the session, return to staff page for a chance to select one
        try:
            self.event = Event.objects.get(pk=request.session.get('event_id',None))
        except Event.DoesNotExist:
            return HttpResponseRedirect(reverse('staff'))
        ## Normal processing
        return super(ListSessionView, self).dispatch(request, *args, **kwargs)  

What do you think? Is this the best way to make this work? Leave your comments below.