Using form.save(commit=False) in a Class Based View

UPDATE: I found a better way to do this.  See this post.

In this example, I once again hadn’t used a generic view for this create function, since I needed to add a value that is not in the form to the new object before the save.

    if form.is_valid():
        new_rec = form.save(commit=False)
        new_rec.institution = institution
        new_rec.save()
        return HttpResponseRedirect(reverse('cat_list'))

The value for institution comes from the user’s session area. The above is the accepted pattern to add the data to the object.

So how can we do this in a class based view?

An override of form_valid() in the class will handle this case.

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.institution = self.kwargs['institution']
        self.object.save()
        return HttpResponseRedirect(self.get_success_url())

(Putting the institution value in kwargs is the subject for another post. One could also use an object variable.)

UPDATE: I found a better way to do this.  See this post.

The reader who just wants to know how to do this can stop here. However….

Is this the best way to do this?

I’m not very skilled in the art of overriding methods, so I am a little concerned about the forward compatibility of this solution.
Let’s first see the upstream definitions of this method.
The method is defined in ModelFormMixin as follows:

    def form_valid(self, form):
        self.object = form.save()
        return super(ModelFormMixin, self).form_valid(form)

And above that we find a simpler method in FormMixin:

    def form_valid(self, form):
        return HttpResponseRedirect(self.get_success_url())

I like how the ModelFormMixin method is able to refer back to the original using the super function, but I don’t know how I can jump over the immediate ancestor to call back the original method. Is this possible? Does it matter?

I am slightly concerned that changes to the method in FormMixin that may come along in future versions may be overlooked with this solution. Sure, I have to check these things during an upgrade anyway, but I would like to perform this as “correctly” as possible.

Please leave your suggestions, thoughts and comments below.

UPDATE: I found a better way to do this.  See this post.

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.

My Eclipse Setup for Django

Eclipse LogoDocumenting my Eclipse setup as I install onto a fresh Ubuntu 11.04 machine.

First, is the choice of Eclipse package. There are several that will work for Python/Django – basically any of the Language IDE focused bundles. This time I am using the Java EE setup (v3.7.1), but I have also installed the standard Java and C/C++ builds with success.

I wish that there was an install procedure for Eclipse that would setup the menus, etc. The package available in the Ubuntu Software Center is usually an older version (currently 3.5) and is a basic install only. It is up to the user to add the additional features desired. Instead, I download the package, extract to /opt, and setup the menus myself.

Next is Aptana Studio 3. This includes PyDev (which supplies Python and Django functionality), web tools, Ruby support, and other goodies. For Python/Django support only, I have used the standalone PyDev install instead. However, once PyDev is installed, the Aptana install complains if installed later.

Also important is version control support. Eclipse comes with CVS included, and clients for other systems are available. Since I use Subversion, I install the Subclipse add-in from Tigrs. Check the version numbers of both your Eclipse and svn installs to select the correct Subclipse package. Eclipse will complain about JavaHL not being available the first time SVN is used. There is a setting in Eclipse (under Team/SVN) to switch to the SVNKit client.

I have also used Eclipse SQL Explorer for database access. It is a bit of a fight to get the proper drivers and configuration all figured out, but worth it to have DB commands within the Eclipse environment here. Read more about SQL Explorer in this earlier post.

That is pretty much it. I may at a later date talk about setting up virtual environments in Eclipse, so stay tuned.

Any questions or suggestions? Please post a comment below.

Satchmo

I’m setting up an online store using the popular Django application Satchmo. My goal is to build a site to sell my wife’s gourd art, but I’d also like to get a store in my portfolio to show to future clients.

Here are a couple of hints on the installation.

URLS

After I installed everything, I ran the satchmo_check command to confirm the configuration and received this error:

Unable to resolve url. Received error- Empty module name

Turns out I had missed a setting when modifying the skeleton settings.py file. Be sure to set ROOT_URLS to the correct setting for your installation. It was blank in the sample file.

CSRF

My first experiment was with the trunk version, currently 0.9-2. However, for the installation of my store, I used the latest released version, 0.9-1. Should be close, right? It probably is, but a big thing to note is that this version does not use the CSRF middleware that is included in my default setup. I didn’t get very far until I commented that module in settings.py.

I’ll post more as I run across things.

Class-Based Views – A ListView Example

I put this here mostly for my own documentation.

Before:

@permission_required('training.view_signupdetail') 
def class_history_attend(request,class_id):
    
    tc = get_object_or_404(TrainingClass,id=class_id)
            
    queryset = SignupDetail.objects.filter(schedule__training_class = tc,attended = True).order_by('session__user__username')
    
    params = {'queryset': queryset,
              'template_name': 'class_history.html',
              'extra_context': {'tc': tc}}
    return object_list(request, **params)    

After:

class ClassHistoryAttendView(ListView):
    ## Set the template name
    template_name = 'class_history.html'  

    ## Override dispatch to apply the permission decorator
    @method_decorator(permission_required('training.view_trainingclass'))
    def dispatch(self, *args, **kwargs):
        return super(ClassHistoryAttendView, self).dispatch(*args, **kwargs)  
    
    ## Define get_queryset to bring in the supplied class number and build the query
    def get_queryset(self):
        self.tc = get_object_or_404(TrainingClass,id=self.kwargs.get('class_id',None))
        return SignupDetail.objects.filter(schedule__training_class = self.tc,attended = True).order_by('session__user__username')
    
    ## Override get_context_data to add the class number to the context for use by the template
    def get_context_data(self, **kwargs):
        context = super(ClassHistoryAttendView, self).get_context_data(**kwargs)
        context['tc'] = self.tc
        return context

UPDATE:

The override of dispatch allows the application of the permission decorator. Note the @method_decorator wrapper around the traditional view decorator for permission use.

The other two method overrides are necessary to access the self instance of the object.

An easier example still uses the permission override, but others are not needed.

Before:

@permission_required('b2c.view_event')
def list_event(request):
    params = {'queryset': Event.objects.all(),
              'paginate_by': DEFAULT_PAGINATION,
              'template_name': 'event_list.html'}
    return object_list(request, **params)

After:

class ListEventView(ListView):
    ## The basics
    template_name = 'event_list.html'  
    paginate_by = DEFAULT_PAGINATION
    queryset = Event.objects.all()

    ## Override dispatch to apply the permission decorator
    @method_decorator(permission_required('b2c.view_event'))
    def dispatch(self, *args, **kwargs):
        return super(ListEventView, self).dispatch(*args, **kwargs) 

Note that the paginate_by variable is set in this example.

Also

These are the imports needed:

from django.views.generic import ListView
from django.utils.decorators import method_decorator

In urls.py, adjust the definition to call the as_view method of the class:

url(r'^list_event/$',ListEventView.as_view(),name='event_list')

Class-Based Views – Am I Doing It Right?

I thought this would be simpler than it turned out to be. Here’s the ‘before’ code:

def start(request):
    ## clear out all session variables
    request.session['username'] = None
    
    return render_to_response('start.html',
                              context_instance=RequestContext(request) )

This view resets a session variable and sends out a template. Not too tough.

Next, is my first attempt to convert to a class-based view:

class StartView(TemplateView):
    template_name = "start.html"
    self.request.session['username'] = None

However, I quickly learned that ‘self’ is not available here. So, I picked a method, and overrode it to include my reset code:

class StartView(TemplateView):
    template_name = "start.html"
    
    def render_to_response(self,context, **response_kwargs):
        self.request.session['username'] = None
        return super(StartView, self).render_to_response(context, **response_kwargs)

Sure, this works, but there is likely a better way.

Please leave your suggestions in the comments below.