Custom form and widgets renderer for DRY forms layout.

Hello folks.

I have successfully override my forms widgets templates as documented. So far I can use {{ form. as_p }} in my templates I and have my overrided widgets, but I want to be able to use custom form renderers in a DRY way, and make this rederers uses some widgets in particular. For example:

Lets say I have a InlineFormRenderer() witch template is /templates/django/horizontalforms/horizontalform.html with the goal of layout a form as a Bootstrap horizontal form layout, I can write another form renderer SmFormRenderer that points to /templates/django/sm-forms/sm-forms.html in order to layout a Bootstrap sm forms an so on.

I can use one of my custom form rederers like this:

class CreateContactForm(ModelForm):
    default_renderer = InLineFormRederer()
    class Meta:
        model = model = Contact
        fields = "__all__"

The problem is: How can I tell to my custom form rederer that uses the widgets templates that are in its respective widget directory?

For my examples the InLineFormRederer should use the widget templetes on /templates/django/horizontalforms/, SmFormRenderer should use /templates/django/sm-forms/widgets and so on.

If I had to tackle something like this, I’d take a look at crispy_forms/layout.py to see how they use the template_pack feature. It’s not a precise match for what you’re looking to do, but it might be close enough to give you some ideas.

Thanks @KenWhitesell , I have used Crispy Forms for a year, and It is a great package, but in fact my main goal is to use Django Forms without a Package in a more controlled way. I have tried to make template packs for crispy forms but I always have a problem implement it.

Honestly I don’t see why this feature hasn’t been implemented in Django, or should be better documented.

This wasn’t a suggestion for you to use Crispy Forms.

You mentioned that you wanted to use different templates depending upon the renderer. My suggestion was to point out that Crispy Forms does something similar to that using the template packs. You might get some valuable ideas for your implementation by looking at Crispy’s source code.

Thanks @KenWhitesell, I am doing that right now.

From Django 4.0, form rendering is now fully template based. It’s new, so the patterns are still to be worked out, and no doubt there’s room for better documentation, but…

If you were to set Form.default_renderer you could provide your own renderer that implemented get_template() to look in the correct directory for your form type.

… I think :smile: — It should work, but I’ve not done that (yet) myself, and you’ll need to play. It would be interesting to see what you come up with if you get it working, or what issues you hit if you don’t.

// cc. @smithdc1

HTH.

Hello @carltongibson , thanks for the answer, I am a fan of Django Chat and in fact the talks about Django 4 form rendering help to start my idea of make custom templates form for my projects.

I had written a big answer, but I decided better to try and it works like a charm

First I code a custom renderer and implemented a “custom” get_template() method:

from django.forms.renderers import BaseRenderer
from django.template.loader import get_template

class CustomFormRenderer(BaseRenderer):
    """
    Custom form renderer in order to load the correct
    path to the widgets templates implementation.
    """
    def get_template(self, template_name):
        return get_template(template_name.replace("django/", "bootstrap/"))

I bet this is not the canonical way how get_template() should be implemented, but reading the Django source code every alternative looks very complicated in order to proof the concept.

After that I just needed to use my custom renderer in my form class:

from .form_rederers import CustomFormRenderer()


class MyForm(ModelForm):
    default_renderer = CustomFormRenderer()

    class Meta:
        model = MyModel
        fields = "__all__"

And it loaded all templates from the “bootstrap” directory, this is a partial output from the Django Debug Toolbar:

bootstrap/forms/label.html
    /home/rigoberto/repos/django-proofs/forms4/contacts/templates/bootstrap/forms/label.html
    Toggle context
bootstrap/forms/attrs.html
    /home/rigoberto/repos/django-proofs/forms4/contacts/templates/bootstrap/forms/attrs.html
    Toggle context
bootstrap/forms/widgets/textarea.html
    /home/rigoberto/repos/django-proofs/forms4/contacts/templates/bootstrap/forms/widgets/textarea.html
    Toggle context
bootstrap/forms/widgets/attrs.html
    /home/rigoberto/repos/django-proofs/forms4/contacts/templates/bootstrap/forms/widgets/attrs.html

Thanks a lot, If there is a better way to implement get_template (of course there is) I would like to know it.

2 Likes

Super. :dancer: Glad you got it going so easily. It’s a nice example of what the new API will allow.

1 Like