Custom ErrorList in a Form

I was playing around with a custom ErrorList the other day. The idea is to be able modify the way errors display in a template. The Django documentation gives an example of this technique.

(One thing I noticed while overriding methods in ErrorList, when I only defined one of the existing display methods, such as as_ul, the code was ignored. Only when I also included my own __unicode__ method pointing to my new code did it work correctly. I’m using Django 1.3 right now, so you may find things better in a future version.)

My challenge was to use my custom ErrorList with a form in a class-based views environment. There are several ways to accomplish this.

Handle It in the View

In the view, one can override the get_form_kwargs method to add the error_class attribute to the kwargs dictionary.

def get_form_kwargs(self):

    kwargs = super(MyViewName,self).get_form_kwargs()

    kwargs.update({'error_class': MyCustomErrorList})

    return kwargs

Since I’m a big mixin fan, I developed this little gem that performs this task:

class CustomErrorClassViewMixin(object):

    ''' Will set the error_class attribute
        on the form with the provided value 
        found in the error_class variable  '''

    error_class = None

    def get_form_kwargs(self):
        # Make sure that the error_class attribute is set on the
        # view, or raise a configuration error.
        if self.error_class is None:
            raise ImproperlyConfigured("'CustomErrorClassViewMixin' requires "
                "'error_class' attribute to be set.")

        kwargs = super(CustomErrorClassViewMixin,self).get_form_kwargs()

        kwargs.update({'error_class': self.error_class})

        return kwargs

Include this mixin in your view class definition (class MyView(CustomErrorClassViewMixin, CreateView)) and set a value for the error_class variable, and it will handle the rest.

Form Level Fun

In some situations, it may be desired to use the custom ErrorList on every use of the form. To do this, a little work in the form’s __init__ method is required.

def __init__(self, *args, **kwargs):
    ## Set the error_class attribute
    kwargs['error_class'] = MyCustomErrorList

    ## Call the parent method
    super(MyForm, self).__init__(*args, **kwargs)

Here’s a mixin for this scenario (I love the mixins!):

class CustomErrorClassFormMixin(object):
''' Allows the declaration of a custom error_class for a form

    Requires the error_class attribute to be provided 
'''

    error_class = None ## Default to none

    def __init__(self, *args, **kwargs):
        # Make sure that the error_class attribute is set on the
        # form, or raise a configuration error.
        if self.error_class is None:
            raise ImproperlyConfigured("'CustomErrorClassFormMixin' requires "
                "'error_class' attribute to be set.")

        ## Set the error_class attribute
        kwargs['error_class'] = self.error_class

        ## Call the parent method
        super(CustomErrorClassFormMixin, self).__init__(*args, **kwargs)

A couple of notes for this one:

  • The mixin must be declared before the form class in order to update the error_class in kwargs before the form’s __init__() method fires
  • The error_class attribute must be defined

Best Looking Errors in Town

Now, your users can make gorgeous errors. Let me know how this works for you.

Django EmptyChoiceField

On my current project, I was faced with an interesting problem. My form includes a ChoiceField that is not required. However, the Django ChoiceField doesn’t allow for no choice. What was happening instead was a de facto default to the first option in the choice list unless the user picked another value – no way to select nothing.

A quick search turned up the EmptyChoiceField from Git user davidbgk – https://gist.github.com/davidbgk/651080, and it worked very well. In my form, I used the field type thusly:

suitability = EmptyChoiceField(required=False,empty_label=u"---------",choices=Suitability)

In David’s code, the empty label choice is prepended to the list when the field is not required and the empty_label parameter has a value. However, I wanted to see if I could make the EmptyChoiceField work more like the ModelChoiceField.

  1. The empty_label parameter defaults to u"---------
  2. A required field has the empty field choice prepended to the list, unless an initial value is provided.
  3. When the field is not required, the empty field choice is always included, regardless of whether an initial value exists.

Here is my version of the field – https://gist.github.com/dashdrum/4960474:

from django.forms import ChoiceField

''' Based on https://gist.github.com/davidbgk/651080 
    modified to mirror the functionality of ModelChoiceField '''

class EmptyChoiceField(ChoiceField):
    def __init__(self, choices=(), empty_label=u"---------", required=True, widget=None, label=None,
                       initial=None, help_text=None, *args, **kwargs):

        # prepend an empty label unless the field is required AND
        # an initial value is supplied

        if required and (initial is not None):
            pass # don't prepend the empty label
        else:
            choices = tuple([(u'', empty_label)] + list(choices))

        super(EmptyChoiceField, self).__init__(choices=choices, required=required, widget=widget,
                                               label=label, initial=initial, help_text=help_text,  
                                               *args, **kwargs)

My invocation of the field only changes in that I no longer need to specify a label text:

suitability = EmptyChoiceField(required=False,choices=Suitability)

By changing the default value of the empty_label, I have broken backward compatibility, although I have a tough time seeing a reason to use this field with the empty_label set to None on purpose. I’ll probably send a pull request back to the original author, but I will note that he may not want to merge it.

Hope this post helps someone else faced with a similar dilemma.

More RelatedFieldWidgetWrapper – My Very Own Popup

I posted back in July on how to use the RelatedFieldWidgetWrapper, but it has only been in the last few weeks that I have actually used it in a project outside of the admin.

When setting up the RelatedFieldWidgetWrapper (RFWW) around a FilteredMultipeSelect widget on my own form, the code changes slightly from what is used on a admin page – basically the pointer to the admin site is no longer needed, nor is the relationship information. Here is the code used for a custom page.

First, I subclassed the RFWW to work outside of the admin, basically stripping out much of the init() code that wasn’t needed. (I’ll leave an examination of how this differs from the original to the reader).

widgets.py

class CustomRelatedFieldWidgetWrapper(RelatedFieldWidgetWrapper):

    """
        Based on RelatedFieldWidgetWrapper, this does the same thing
        outside of the admin interface

        the parameters for a relation and the admin site are replaced
        by a url for the add operation
    """

    def __init__(self, widget, add_url,permission=True):
        self.is_hidden = widget.is_hidden
        self.needs_multipart_form = widget.needs_multipart_form
        self.attrs = widget.attrs
        self.choices = widget.choices
        self.widget = widget
        self.add_url = add_url
        self.permission = permission

    def render(self, name, value, *args, **kwargs):
        self.widget.choices = self.choices
        output = [self.widget.render(name, value, *args, **kwargs)]
        if self.permission:
            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
                (self.add_url, name))
            output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
        return mark_safe(u''.join(output))

Next, I used this new widget wrapper in my form definition:

forms.py

class EndowmentForm(ModelForm):
    support_accounts = ModelMultipleChoiceField(queryset=None,
                                                label=('Select Support Accounts'),
                                                required=False)

    def __init__(self, *args, **kwargs):
        super(EndowmentForm,self).__init__(*args, **kwargs)
        # set the widget with wrapper
        self.fields['support_accounts'].widget = CustomRelatedFieldWidgetWrapper(
                                                FilteredSelectMultiple(('Support Accounts'),False,),
                                                reverse('support_create'),
                                                True)
        self.fields['support_accounts'].queryset = SupportAccount.objects.all() 

    class Media:
        ## media for the FilteredSelectMultiple widget
        css = {
            'all':(ADMIN_MEDIA_PREFIX + 'css/widgets.css',),
        }
        # jsi18n is required by the widget
        js = ( ADMIN_MEDIA_PREFIX + 'js/admin/RelatedObjectLookups.js',)

    class Meta:
        model = Endowment  

And include the javascript files in the template:

endow_edit.html

(somewhere in the <head> section)

{{ form.media }}
<script type="text/javascript" src="{% url admin:jsi18n %}"></script>

Also be sure to include javascript.

The parent side is ready. Now let’s tackle the child side.

To add a new related record, the widget will open the page in a popup window. (Try this in the admin to see how it should work.) When the save button is pressed, the record is added, the popup closes, and the new value is added to the chosen side of the FilteredSelectMultiple control. Pretty cool!

But … how can I make my form do that?

After digging around a little in the Django admin code, I have figured it out.

Luckily, all of the heavy lifting is performed by the javascript provided with Django. I just needed to add a little code to make it all work.

First, I added a key in the context to let me know if the form is a popup. Notice that the RelatedFieldWidgetWrapper appends ‘?=_popup=1’ to the URL. My code looks for that key in the GET data and adds the context variable if found:

views.py

SupportAccountCreateView(CreateView)

def get_context_data(self, **kwargs):
    context = super(SupportAccountCreateView,self).get_context_data(**kwargs)
    if ('_popup' in self.request.GET):
        context['popup'] = self.request.GET['_popup'] 
    return context

Next, in the template, the presence of this context key triggers the addition of a hidden field:

support_edit.html

(somewhere in the <form>)
{% if popup %}<input type="hidden" name="_popup" value="1">{% endif %}

Now that I know I’m dealing with a popup form, my post() code can look for it and fire off the javascript:

views.py

SupportAccountCreateView(CreateView)

def post(self, request, *args, **kwargs):
    ## Save the normal response
    response = super(SupportAccountCreateView,self).post(request, *args, **kwargs)
    ## This will fire the script to close the popup and update the list
    if "_popup" in request.POST:
        return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
            (escape(self.object.pk), escapejs(self.object)))
    ## No popup, so return the normal response
    return response

(This little snippet was pretty much copied from the admin code.)

Finally, I need to include the javascript in the form:

forms.py

SupportAccountEditForm(ModelForm)

class Media:
    ## media for the Related Field Wrapper
    # jsi18n is required by the widget
    js = ( ADMIN_MEDIA_PREFIX + 'js/admin/RelatedObjectLookups.js',)

support_edit.html

(somewhere in the <head> section)
{{ form.media }}
<script type="text/javascript" src="{% url admin:jsi18n %}"></script>

Be sure to also include jquery in the template.

That’s it! Give it a try sometime.

Setup Django Environment on a New Mac

MacBook Pro

Setting up a Mac for Django development was easy – even a little easier than I thought. My machine is a new MacBook Pro running 10.8.2.

Xcode

Xcode provides the utilities needed for Python libraries and Subversion. It can be installed from the App Store. Afterwards, open the application, open Preferences and select downloads, then install the command line tools.

Python Utilities

In a terminal window:

  1. sudo easy_install pip
  2. sudo easy_install virtualenv

Eclipse

Setting up Eclipse worked pretty much the same as it does on Linux.

  1. Download desired Eclipse package. I used the Java EE package, Indigo (3.7) version.
  2. Unzip the files
  3. Copy Eclipse folder to Applications
  4. Drag the program file to the dock

As I cover in my earlier post, My Eclipse Setup for Django, I installed Aptana Studio 3 to add Python and Django support, and Subclipse to access my Subversion repositories.

That’s It!

Not tough at all. Happy developing!

Unit Testing Forms in Django

This is a subject that doesn’t seem to be covered well by other sources, so I am recording my notes on testing a Django form. Credit to “fRui Apps” for this post that gives an example. (See the section Testing Forms)

My goal is to get the test as close to the code as possible. Rather than using the view test, or even Selenium, this approach will isolate the form’s functions without going through all of Django’s URL routing, middleware, etc. Save that for the functional tests.

In this first example, my form will pad the entered number with left hand zeroes. This is the quick test:

def test_id_format(self):  
    ## Use factoryboy to create a model instance  
    e = EndowmentFactory.create()
    ## Instantiate form  
    form = EndowmentEntityForm(data={'endowment': e.pk,  
                                     'id_number': '528989',  
                                     'entity_type': EntityType.ORIGINATOR,})
    ## call is_valid() to create cleaned_data  
    self.assertTrue(form.is_valid())  
    ## Test away!  
    self.assertEqual(form.clean_id_number(),'0000528989')

Note that I’m using factoryboy to create a model instance to work with. The form data includes an id_number of fewer than 10 characters. After I call is_valid() to populate cleaned_data, I can test the cleaned id_number for the proper format.

In this next example, I am testing for valid and invalid data. My application allows changing the status of an object, but certain rules apply. Here is a test confirming that a valid change is allowed to continue:

##  Test for valid action types
def test_action_type_valid(self):
    ## Use factoryboy to create a model instance
    e = EndowmentFactory.create(status='E')        
    ## Instantiate form
    form = UserActionForm(data={'endowment': e.pk,
                                'username': 'dgentry',
                                'action_type': 'A',})
    ## is_valid() should return True if no errors are found
    self.assertTrue(form.is_valid())

And this will test for an invalid status change:

##  Test for valid action types
def test_action_type_valid(self):
    ## Use factoryboy to create a model instance
    e = EndowmentFactory.create(status='A')        
    ## Instantiate form
    form = UserActionForm(data={'endowment': e.pk,
                                'username': 'dgentry',
                                'action_type': 'E',})
    ## We run is_valid() and expect False
    self.failIf(form.is_valid())
    ## check for the error
    self.assertIn(u'Invalid Action',form.errors['__all__'])

I’m sure that I will learn more about testing forms as I go. Stay tuned…

Lazy Reverse

Working in Django 1.3, I had some trouble with my view code.

Actually, the error was pretty vague, something about my urls.py containing no patterns. After much wailing and gnashing of teeth (during which I learned I really don’t know how to best use the debugger in Eclipse), I traced the problem down to three of my view classes. All three included a setting for the success_url attribute:

success_url = reverse('support_list')

Apparently, you can’t do that.

Once I found the offending line of code, something in my mind clicked on to remind me that I had hit this problem before. To solve it, I had moved the reverse statement into the get_success_url() function:

def get_success_url()
    return reverse('support_list')

Looking for a better solution, a quick Google Search brought up this post in in the django-users group.

I have a CreateView which I’d like to redirect to a custom success_url

defined in my URLconf. As I want to stick to the DRY-principle I just

did the following:

  success_url = reverse("my-named-url") 

Unfortunately, this breaks my site by raising an

“ImproperlyConfigured: The included urlconf doesn’t have any patterns

in it”. Removing success_url and setting the model’s

get_absolute_url() to the following works fine:

  def get_absolute_url(self): 
      return reverse("my-named-url") 

I could reproduce this with a brand new project/application so I don’t

think this has something to do with my setup.

Can anyone confirm this issue?

So others have hit the same problem. Looking further down the page, a more concise solution was offered:

 success_url = lazy(reverse, str)("support_list") 

I used this for my current project – a little cleaner.

In v1.4, we can use the ‘reverse_lazy()’ function in place of ‘reverse()’.

UPDATE:

Suddenly, I understand the reason for the SuccessURLRedirectListMixin in the popular Django Braces package. Using this mixin, one can declare the success url with a simple variable assignment (no lazy code required), and the mixin provides the get_success_url() function needed to make everything work.

BTW, I highly recommend using Braces with class based views. I have found several to be quite useful to speed up coding and to keep things clean and clear.

Quick Post – Django Factory Boy

I’ve been using factoryboy in my tests as of late. It’s a nice tool to create test data in a programmatic way – no fixtures.

A factory includes a Sequence class that can be used to create unique field values with each instance. The examples I’ve seen use something like this:

title = factory.Sequence(lambda n: 'Event ' + n)

Which will create successive records with titles like “Event 0”, “Event 1″, Event 2”, etc.

The variable n is a string type by default, but I wanted to use it as an integer – in this case to set a value to an Integer field:

capacity = factory.Sequence(lambda n: n * 10)

However, this instead put 10 ones in the field, instead of the number held in n times 10.

A quick perusing of the documentation here showed that the Sequence class takes a second parameter after the function that indicates the desired type for n. Adding int to the end of the call makes it work much better.

capacity = factory.Sequence(lambda n: n * 10,int)

Now the field holds a multiple of 10.

Want a more complicated example? Here I assign a date field that is n nubmer of days after the current date.

event_date = factory.Sequence(lambda n:(datetime.now() + timedelta(days=n)).date(), int)

Project Cancelled

"Project cancelled" on rubber stampI’ve been working on a project at work for the past couple of months. This has been a “work on it when I can” project, so I have probably spent 4 or 5 days total on it.

However, I learned last week that it will likely be cancelled. Since everything I work on is a chance to try new things and update my current patterns, I’d like to keep this code around. But I’m worried that it will end up neglected in a long forgotten version control repository branch.

The question of the day is: What do you do with abandoned code that may have some good ideas in it?

RelatedFieldWidgetWrapper

The RelatedFieldWidgetWrapper (found in django.contrib.admin.widgets) is used in the Admin pages to include the capability on a Foreign Key control to add a new related record. (In English: puts the little green plus sign to the right of the control.) In a new application of the FilteredSelectMultiple widget I discussed recently, I needed to add this functionality.

This wrapper is a little complicated, but it didn’t take too much Google searching to find an example.  A Google Code file from something called Crimson Online helped me understand the concept.

The wrapper takes three required parameters plus one optional:

  1. The widget to be wrapped. In my case the FilteredSelectMultiple widget
  2. A relation that defines the two models involved
  3. A reference to the admin site
  4. The Boolean can_add_related (optional)

For the first parameter, I used the widget with its parameters:

FilteredSelectMultiple(('entities'),False,)

The relation confused me at first, but the example made it easy.  The model linked to the form, in this case Item, provides it.

Item._meta.get_field('entities').rel

The third parameter, the admin site object, is used along with the relation to determine if the user has permission to add this related model.  The example used django.contrib.admin.site, which I found doesn’t work.  Instead, I captured the admin_site value from the ModelAdmin class in my admin.py and set it as a variable for the form.  Then I referenced that variable in the \__init__ method of the form to include it in the widget.  This means that the widget has to be assigned within the __init__ method of the form.

self.admin_site

The fourth parameter can be used to override the permission check performed by the widget.  However, when a user without the permission to add clicks that little green plus sign, an ugly 500 error is returned.

Here is the code used:

in admin.py:

class ItemAdmin(admin.ModelAdmin):
    form = ItemAdminForm

    def __init__(self, model, admin_site):
        super(ItemAdmin,self).__init__(model,admin_site)
        self.form.admin_site = admin_site # capture the admin_site

admin.site.register(Item, ItemAdmin)

in forms.py

from django.contrib.admin.widgets import FilteredSelectMultiple, RelatedFieldWidgetWrapper

class ItemAdminForm(forms.ModelForm):
    entities = forms.ModelMultipleChoiceField(queryset=None,label=('Select Entities'),)

    def __init__(self, *args, **kwargs):
        super(ItemAdminForm,self).__init__(*args, **kwargs)
        # set the widget with wrapper
        self.fields['entities'].widget = RelatedFieldWidgetWrapper(
                                                FilteredSelectMultiple(('entities'),False,),
                                                Item._meta.get_field('entities').rel,
                                                self.admin_site)
        self.fields['entities'].queryset = Entity.objects.all()

    class Media:
        ## media for the FilteredSelectMultiple widget
        css = {
            'all':('/media/css/widgets.css',),
        }
        # jsi18n is required by the widget
        js = ('/admin/jsi18n/',)

    class Meta:
        model = Item

UPDATE:

I’ve published a follow up post that covers using this wrapper outside of the admin application. See More RelatedFieldWidgetWrapper – My Very Own Popup.