Django Tips

I love Django. The web framework, not the jazz guitarist, though he’s not bad, either. Their documentation is a bit lacking in places, mostly in the details, which may be semi-niche edge cases, but I think it’s worth documenting what I’ve found, even if just for myself (as these posts usually are).

Widget Templates

I’ve wanted to tweak a few, and have, previously to what I discovered yesterday, gone deep into the weeds to modify the templates (in django/templates/django/forms/widgets). There are much easier ways! First of all, there’s an invaluable app, django-widget-tweaks, that, even though the code is a tad stale, does enough to be worth its use. Second, if you want to be fancier about things, or if you use other apps that override widgets (like django-markdownx), newer versions of Django need additional settings that are far from clear in the documentation.

First thing, edit your settings.py file. Add FORM_RENDERER='django.forms.renderers.TemplatesSetting' as its own line somewhere in the file. If you do this, and only this, you will find yourself presented with an error page where Django is expecting you to provide templates for everything. Maybe this is what you want, but probably not. To fix this behavior, in INSTALLED_APPS, add ‘django.forms’, and make sure this is the last line of the installed apps, or at the very least, after anything that might feasibly override a widget. That’ll take care of details that you don’t feel like worrying about as you develop your app. You might reach a point where you’ve covered every widget with your own, but it’s good to have a fallback just in case one was missed!

ModelForms

So you’re useing ModelForms to help build forms that you don’t have the time or inclination to write get and put methods all the time. Great! That’s what they’re there for! But there are some things that aren’t obvious, by any stretch!

How about passing data to one of these forms via the URL?

Let’s consider the polls model from the tutorial. Say that, instead of adding answers when editing the question (which, yes, in that case, makes more sense, but go with me for a minute) we want to add answers to a question on a page with the url ‘polls/<int:question_id>/add_answer/’. We know we’re adding an answer, and because of the URL we know the question ID, but the normal ModelForm for the given Answer model will ask you to provide the question_id, because of the ForeignKey field.

Here’s what you need to do:

class AnswerAdd(generic.CreateView):
    model=Answer
    def get_form(self):
        self.question=get_object_or_404(Question,id=self.kwargs['question_id'])
        partial_ans=Answer(question=self.question)
        kwargs=self.get_form_kwargs()
        kwargs['instance']=partial_ans
        form=AnswerForm(**kwargs)
        return form

First, you can’t get away without creating an AnswerForm class, but since this only requires a couple of extra lines from what you’d need in your view, and you’d have to duplicate that in your create and update views anyway, it’s probably worth it.

Second, and annoyingly, you can’t instantiate the form by doing AnswerForm(self.get_form_kwargs(), instance=partial_ans), but this is for good reason: because there’d then be two instance keyword arguments in the constructor—not allowed. Thus the separate lines for kwargs. This also allows you to pass in pass in extra data to the form.

Why can’t we do this in get_context_data? Because that method doesn’t handle the object instance.

There is a downside here, too: you can’t use the mere existence of the object in your template to figure out if someone is adding or editing an object. There’s a simple fix, though, just update your template to use this logic instead:

{% if not object.pk %}Create{% else %}Update{% endif %}

Updating Widgets at Runtime

This may be controversial, but I don’t know why. I think it has it place, and it’s situational. I have a number field. It has a meaning, and as the software developer I know what it means, but since that meaning can vary between instances in a rather complicated way that doesn’t lend itself to creating more objects and tables, and since I want to convey that meaning to my users, I want to replace the <input> widget with a <select> widget, a drop-down that’s far more intuitive for my users.

Here’s how you do that, though:

class MyModelForm(forms.ModelForm):
    class Meta:
        model=MyModel
        fields=[
            'field1',
            'field2',
            # you get the idea
        ]
    def __init__(self,*args,**kwargs):
        obj=kwargs.pop('some_object')
        super().__init__(*args,**kwargs)
        self.fields['field1'].widget=forms.Select(
            choices=obj.get_choices()
        )

Removing Widgets Dynamically

This is answered a few places at SO, but it’s worth rehashing here for my own benefit. Sometimes, you want/need to have a field that is only displayed during the creation of an object, or only when an object is updated, but you’re lazy and don’t want to make a CreateForm and an UpdateForm. I’m with you, all the way! And, as usual, there’s a way to do this:

class MyModelForm(forms.ModelForm):
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        if not self.instance:
            self.fields.pop('special_field1')
        else:
            self.fields.pop('special_field2')

With the logic above, special_field1 is removed when creating a new MyModel, and special_field2 is removed when updating that MyModel. The downside is that this won’t work in conjunction with passing data via the URL, because you have an instance, even if it’s mostly empty. You have to be a teensy bit cleverer (though I gave this answer out earlier in this post, I only figured it out here, so I’m repeating it for everyone’s benefit):

class MyModelForm(forms.ModelForm):
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        if not self.instance.pk:
            self.fields.pop('special_field1')
        else:
            self.fields.pop('special_field2')

Note that this time I’m checking for a value of instance.pk that isn’t a Boolean false. While you create an instance, no primary key is (typically) created until the object is saved in the database. Even if you have some situation where the PK is filled out in the instance you pass to the form, it’s likely there will be something that you can use to test if the form should be an edit or an update.