Django Form Label for Many-to-Many with Extra Fields

January 12, 2017 11:36 am

I'm working on a Django application at work that has several Many-to-Many relationships that have extra fields attached to them. Setting this up using the "through" parameter on the field definition is described in the Django documentation.

My trouble came in when it came time to render this information out to the user in a form so they could update the extra fields.  From the user's perspective the relationship can't be changed, but the extra fields could be.

Extending Django's example usage about band membership, if the user were viewing the information for "The Beatles" and including a formset for all band members the form would normally render something like this:

Group Name: The Beatles
Members:
    Group: The Beatles
    Person: Ringo Starr
    Date Joined: 14 August 1962
    -
    Group: The Beatles
    Person: John Lennon
    Date Joined: 01 August 1960
    -
    [etc.]

But this is a little obtuse if the purpose of the form is to allow the user to edit the "Date Joined" value for each band member.  We can whittle the form down by telling the formset to only render the "Date Joined" field [by passing fields=('date_joined',) to modelformset_factory], but then we get this:

Group Name: The Beatles
Members:
    Date Joined: 14 August 1962
    -
    Date Joined: 01 August 1960
    -
    [etc.]

Now we can't tell to which person each date applies.

Adjusting the form so that the label "Date Joined" was instead the appropriate band members name seems like such a simple thing to change.  But I didn't want to hand-write the form as it was already being automatically built and rendered for me using using modelformset_factory and Crispy-Forms.

It took me most of a day to figure it out and in the end it was pretty simple.  I only need to implement a custom Form that overrides the constructor and sets the label using the object instance before it gets thrown away:

class MembershipForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(MembershipForm, self).__init__(*args, **kwargs)
        if kwargs['instance'] and kwargs['instance'].person:
            self.fields['date_joined'].label = kwargs['instance'].person.name
        else:
            self.fields['date_joined'].label = 'No Person'
    class Meta:
        model = Membership
        fields = ['date_joined']

And now our output looks something like:

Group Name: The Beatles
Members:
    Ringo Starr: 14 August 1962
    -
    John Lennon: 01 August 1960
    -
    [etc.]

Since it took me most of a day to figure this out, and I couldn't find anything specifically addressing this on the Internet, I wrote this up so hopefully the next person (or me in the future once I've forgotten this) will have an easier time figuring it out.

Now Presenting: Serindu Gallery

June 4, 2013 9:34 pm

I've been working on a new project recently.

It started with the goal of a photography gallery website where I could put my pictures and allow people to self-service buy prints/canvases/etc. through one of the many companies that sell such things.

I found a promising option, but it turned out to not be as helpful as I had hoped.  They wanted me to sign up with a payment processing company, file for a state tax identification number, etc, etc.  Way more effort than I wanted to go through.

Then I found another option, but, on top of the cut they take for each sale they also want $30 a year, which I don't really feel like paying.

So I took another tactic: I created a photography gallery website and licensed my pictures under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.  It's not nearly as complicated as it sounds.  It essentially means you can do just about anything you want with the photos except use them commercially so long you attribute them to me as the copyright owner.

So anyone is free to have prints or canvases or whatever made from the photos.

Without further ado, I present Serindu Gallery:

serindu_screenshot

Serindu is just a name I made up years ago for an online game, it doesn't mean anything, I just like the way it flows.

Tools used to build the site:
Django, Django-photologue, Django-tagging, Bootstrap, Bootstrap-Image-Gallery, jQuery, and BrowserState history.js.  I also used the webfont Tangerine.

Django - Cronjobs Made Easy!

August 8, 2009 3:04 pm

For those that have no interest in reading about my nerd-ventures, you can stop reading this post now.

If you're still reading, don't say I didn't warn you.

As has been mentioned previously (mainly on my previous blog), I've been doing a fair bit of side project work using the Django Framework. Sadly, the out-of-the-box Django doesn't provide a solution for running cronjobs (for tasks that need to be run within the Django environment).

Since that's a fairly common requirement I didn't think it was going to be a big deal, but there wasn't a really solid solution out there. There are a few different attempts, but they each have some limitation. There's django-cron but that just skips over the native cron entirely, which I felt was a bit extreme. Cron can already do a good job of waking up and running a command, so duplicating that functionality doesn't seem necessary. It also self-declares that it is designed for frequent tasks (hourly or more frequently), which doesn't work for me. Tasks on the Board need to be able to run from a minute scale to a daily scale and beyond.

Then I found this guy's method, which works, but I'd like a little more integration. That way when developing apps the "go add cron job" isn't a separate step. I want the job information to right in with the rest of my app information. That way I can see what should be happening and when.

That's when I came across Django-Chronograph. This solution was 95% of what I wanted. It provides a nice interface to the system to monitor your jobs and view logs. It requires only a single crontab entry. It uses the iCalendar style of task declaration so you have total control of when your jobs run. However, it is limited to running commands through the Django Management system. I wanted something a little more programmatic. Such that I could just point at whatever function I wanted for my jobs.

So I took Django-Chronograph and started my modifications. The result is Django Cron Manager. Setup is very similar to using the Admin system. You call the cron_manager.autodiscover() function from your urls.py file. This goes out and inspect your installed apps and registers any Cron Jobs they declare. Then, using the guts of Django-Chronograph, it keeps track of these jobs in the database and monitors when they need to run.

I'm planning on posting all the code with an example at some point, but I'm going to try to get in touch with Weston (the guy who wrote Django-Chronograph) to see if he just wants to roll my changes into his system permanently. If you stumble upon this post and the changes aren't in Django-Chronograph, and I haven't provided any further information. Just leave a comment that you're interested in the code with a way to contact you and I'll get something to you.

*** Update ***
I've posted the code here: http://code.google.com/p/django-chronograph/issues/detail?id=15