Django 1.11 TypeError context must be a dict rather than Context

PythonDjangoDjango Views

Python Problem Overview


Just received the Sentry error TypeError context must be a dict rather than Context. on one of my forms. I know it has something to do with Django 1.11, but I am not sure what to change to fix it.

Offending line

message = get_template('email_forms/direct_donation_form_email.html').render(Context(ctx))

Entire View

def donation_application(request):
    if request.method == 'POST':
        form = DirectDonationForm(data=request.POST)
        if form.is_valid():
            stripe.api_key = settings.STRIPE_SECRET_KEY
            name = request.POST.get('name', '')
            address = request.POST.get('address', '')
            city = request.POST.get('city', '')
            state = request.POST.get('state', '')
            zip = request.POST.get('zip', '')
            phone_number = request.POST.get('phone_number', '')
            support = request.POST.get('support', '')
            agree = request.POST.get('agree', '')
            email_address = request.POST.get('email_address', '')
            number = request.POST.get('number', '')
            cvc = request.POST.get('cvc', '')
            exp = request.POST.get('exp', '')
            # token = form.cleaned_data['stripe_token'],
            # exp_m = int(request.POST.get('exp_month', ''))
            # exp_y = int(request.POST.get('exp_year', ''))

            exp_month = exp[0:2]
            exp_year = exp[5:9]

            subject = 'New Donation'
            from_email = settings.DEFAULT_FROM_EMAIL
            recipient_list = ['deniselarkins@/////\\\\\.com',
                              'charles@/////\\\\\.net',
                              'marcmunic@/////\\\\\.com',
                              ]

            token = stripe.Token.create(
                card={
                    'number': number,
                    'exp_month': exp_month,
                    'exp_year': exp_year,
                    'cvc': cvc
                },
            )

            customer = stripe.Customer.create(
                email=email_address,
                source=token,
            )

            total_support = decimal.Decimal(support) / 100
            total_charge = decimal.Decimal(int(support)) / 100

            # Charge the user's card:
            charge = stripe.Charge.create(
                amount=total_charge,
                currency='usd',
                description='Donation',
                customer=customer.id
            )

            ctx = {
                'name': name,
                'address': address,
                'city': city,
                'state': state,
                'zip': zip,
                'phone_number': phone_number,
                'email_address': email_address,
                'agree': agree,
                'charge': charge,
                'customer': customer,
                'total_support': total_support,
                'total_charge': total_charge
            }

            message = get_template('email_forms/direct_donation_form_email.html').render(Context(ctx))
            msg = EmailMessage(subject, message, from_email=from_email, to=recipient_list)
            msg.content_subtype = 'html'
            msg.send(fail_silently=True)

            return redirect(
                '/contribute/donation-support-thank-you/?name=' + name +
                '&address=' + address +
                '&state=' + state +
                '&city=' + city +
                '&zip=' + zip +
                '&phone_number=' + phone_number +
                '&email_address=' + email_address +
                '&total_support=' + str(total_support) +
                '&total_charge=' + str(total_charge)
            )
    context = {
        'title': 'Donation Pledge',
    }

    return render(request, 'contribute/_donation-application.html', context)

Python Solutions


Solution 1 - Python

In Django 1.8+, the template's render method takes a dictionary for the context parameter. Support for passing a Context instance is deprecated, and gives an error in Django 1.10+.

In your case, just use a regular dict instead of a Context instance:

message = get_template('email_forms/direct_donation_form_email.html').render(ctx)

You may prefer to use the render_to_string shortcut:

from django.template.loader import render_to_string

message = render_to_string('email_forms/direct_donation_form_email.html', ctx)

If you were using RequestContext instead of Context, then you would pass the request to these methods as well so that the context processors run.

message = get_template('email_forms/direct_donation_form_email.html').render(ctx, request=request)
message = render_to_string('email_forms/direct_donation_form_email.html', ctx, request=request)

Solution 2 - Python

Migrated from Django 1.8 to Django 1.11.6

Wherever i had a RequestContext class, there is a method flatten() wich return the result as a dict.

So if the class is RequestContext....

return t.render(context)

becomes

return t.render(context.flatten())

And in a case wich the context is is wrapped by Context(), just remove it. Because Context() is deprecated.

return t.render(Context(ctx))

becomes

return t.render(ctx)

Solution 3 - Python

For django 1.11 and after, context must be dict.

You can use:

context_dict = get_context_dict(context)
return t.render(context_dict)

or

context_dict = context.flatten()
return t.render(context_dict)

Solution 4 - Python

I got here because I had the same issue. I'm learning Django with Django Unleashed by Andrew Pinkham. It's a book from 2015.

I found in the official documentation, that a dictionary must be passed to the context parameter and not a Context instance (from django.template.Context).

@Alasdair suggested to use render_to_string, but, at least in Django 3.2 the render method use render_to_string method intrinsically.

def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
Return a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)

so, using just the render method could be better. I provide this answer because was the one that I was looking for and it may help some one reaching this Stack Overflow question.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCharles SmithView Question on Stackoverflow
Solution 1 - PythonAlasdairView Answer on Stackoverflow
Solution 2 - PythonVIctor AngelovView Answer on Stackoverflow
Solution 3 - PythonYvette TanView Answer on Stackoverflow
Solution 4 - PythonEadwulfView Answer on Stackoverflow