Monday, April 12, 2010

It's the Small Things

I guess at this point, you'd assume that I'm an expert at hacking Django forms. After all, I already wrote two blog posts about my custom validation and saving. But this post is all about the small things I've tried to do. Some with success, and some without.

Fields that are Not Part of the ModelForm

One of the newer requirements in our use case was to create a place in the form for generating activity confirmation codes if the activity's confirmation type is "code". The field would take the number of codes the admin wants to generate. The easy way to do this is to simply have the number of codes be a property of the activity model. Then, the admin form can generate the field and have it on the Django admin form. However, there's no reason for the number of codes to be a part of the model. So there must be a way to present it as a non-model field.

Number of codes field in the Activity admin interface

The solution is to simply add the form as an additional IntegerField in the definition of the ModelForm. Then, when the form is being validated or saved, it is one of the parameters. Then I have a static method in my confirmation code model that generates a number of codes for a given activity. Great, so now I can generate a number of codes for an activity. Where should I put a link to view the codes?

I also started inserting help text into the model fields so that they're presented on the form. However, for an activity with confirmation codes, the text should change if the activity has already been created. So this presents an interesting opportunity. If I can change the help text so that it says "Number of additional codes to generate" instead of "Number of confirmation codes to generate", then I can insert a link to view codes.

Well, then we just override the init method (the method called to initialize an object) to edit the num_codes field. There, if the activity has been created (i.e. has a created_at field), then we can change the help text and insert a link to our view codes view.


class ActivityAdminForm(forms.ModelForm):
num_codes = forms.IntegerField(required=False,
label="Number of codes",
help_text="Number of confirmation codes to generate",
initial=0
)

def __init__(self, *args, **kwargs):
"""Override to change number of codes help text if we are editing an activity."""

super(ActivityAdminForm, self).__init__(*args, **kwargs)
# Instance points to an instance of the model.
if self.instance and self.instance.created_at and self.instance.confirm_type == "code":
self.fields["num_codes"].help_text = "Number of additional codes to generate <a href=\""
self.fields["num_codes"].help_text += reverse("activities.views.view_codes", args=(self.instance.pk,))
self.fields["num_codes"].help_text += "\" target=\"_blank\">View codes</a>"
Read-Only Fields

I like that the Django admin presents an interface for editing the different fields of a Django model. However, there are a few fields that need to be displayed but not edited. A good example is the ActivityMember model, which is a model that represents a user's participation in an activity. These are the models that are approved/rejected when a user requests points for their participation. There is no reason for admins to be able to change the activity or comments from the user. However, they need to be displayed so that admins have some context.

As it turns out, Django has a field for ModelForms that can specify fields that should be read-only. However, this feature is only enabled for the development version of Django (version 1.2). The version of Pinax I am using is 0.7.1 and it only has Django 1.0.4. A friend of mine is trying to convince me to update to the dev version of Pinax (0.9), which uses Django 1.2. I think I'll hold off updating until the summer rolls around. I'm hoping that both are stable enough by then so that I can use it in production in October. I also found this interesting post on StackOverflow that deals with this issue. Perhaps if I have time, I can add it in.

Check out the current implementation at GitHub.

No comments:

Post a Comment