How do I pass a context variable from a view to a custom field/widget in a Django template?

4.8k views Asked by At

I have defined a custom field and widget for a form that is being served by subclassing UpdateView. So, something like this:

myapp/forms.py:

from .form_fields import MyCustomField
from .widgets import MyCustomWidget

class MyModelForm(forms.ModelForm):

    my_field = MyCustomField(queryset=MyModel.objects.all(), widget=MyCustomWidget)

myapp/views.py:

from django.views.generic import UpdateView
from .forms import MyModelForm

class MyView(UpdateView):
    form_class = MyModelForm

myapp/widgets.py:

from django.forms import Widget
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe

class MyCustomWidget(Widget):
    context_data = { 'custom_data': custom_data }
    html_output = render_to_string('myapp/widgets/my_custom_widget.html', context_data)
    return mark_safe(html_output)

Basically, I want to be able to pass custom_data from my view (e.g. from the session store or the form instance) to the widget.

3

There are 3 answers

0
3cheesewheel On

I figured it out; I'm not totally sure if this is the best/recommended way to do it, but it works.

First, in the view, update get_form_kwargs() with the custom data. For example, in my case I wanted to use the extra data from the instance attached to the form.

# myapp/views.py
from .forms import MyModelForm


class MyView(UpdateView):

    form_class = MyModelForm

    def get_form_kwargs(self):
        kwargs = super(MyView, self).get_form_kwargs()
        form_instance = kwargs.get('instance')
        extra_widget_data = form_instance.widget_data
        kwargs.update({ 'extra_widget_data': extra_widget_data })
        return kwargs

Next, in your form's __init__(), pop the kwarg and attach it to the custom widget on the field:

# myapp/forms.py
from django import forms

from .widgets import MyCustomWidget


class MyModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        extra_widget_data = kwargs.pop('extra_widget_data')
        super(MyModelForm, self).__init__(*args, **kwargs)
        self.fields['my_custom_field'] = MyCustomField(
            widget=MyCustomWidget(extra_widget_data=extra_widget_data)
        )

Finally, in your custom widget class, grab the variable in the __init__():

# myapp/widgets.py
from django.forms import Widget
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe


class MyCustomWidget(Widget):

    def __init__(self, attrs=None, extra_widget_data=None):
        super(MyCustomWidget, self).__init__()
        self.extra_widget_data = extra_widget_data

    def render(self, name, value, attrs=None):
        context_data = { 'custom_data': self.extra_widget_data }
        html_output = render_to_string('myapp/widgets/my_custom_widget.html', context_data)
        return mark_safe(html_output)

Now the {{ custom_data }} template variable is available in the rendered HTML from myapp/widgets/my_custom_widget.html.

0
Guilherme David da Costa On

You can also overwrite Widget's get_context method:

# myapp/widgets.py
from django.forms import Widget
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe

class MyCustomWidget(Widget):
    template_name = 'myapp/widgets/my_custom_widget.html'

    def __init__(self, attrs=None, extra_widget_data=None):
        self.extra_widget_data = extra_widget_data
        super(MyCustomWidget, self).__init__()

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context['custom_data'] = self.extra_widget_data
        return context
1
M. Ryan On

I was working on a similar problem and I think I found a slightly more reliable and universal way to address this issue.

In your solution, you override render() to substitute in additional context data to your widget.

However, when I was exploring this concept in the Django source code, I found that the Django framework developers actually deal with this exact issue in a way that I think a better practice.

Instead of overriding render() they override get_context(), as seen in the ChoiceWidget class at https://docs.djangoproject.com/en/1.11/_modules/django/forms/widgets/

This approach makes it so that you don't have to worry about re-specifying the rendering code and template identification which is probably best left to the pros.