Reusable form templates not working in Django 4.0?!

Hi together,
I’ve started to build a Django project using a virtual environment (venv).

Currently I am working through the different ways of creating forms - from low-level (without any Form classes) to using Form and ModelForm.
But as soon as I implement a form snippet as described in Reusable form templates of the Working with forms topic in Django 4.0, I am facing the following error.
If I use no form templates, this error does not happen (described below - working setup 1).
And the error doesn’t occur, if I use the Resusable form templates approach from Django 3.2 (described below - working setup 2).
Both working setups render the same way, without any issues.

:interrobang:Therefore I was wondering whether this issue is being caused by the changes in Django 4.0 :interrobang:

Error in debug window (when using Django 4.0 approach)

TemplateDoesNotExist at /codermatch/create-ad/
codermatch/formSnippet.html

Request Method: 	GET
Request URL: 	http://localhost:8000/codermatch/create-ad/
Django Version: 	4.0.1
Exception Type: 	TemplateDoesNotExist
Exception Value: 	codermatch/formSnippet.html

Exception Location: 	C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\template\backends\django.py, line 84, in reraise
Python Executable: 	C:\Users\Name\codingprojects\CoderMatching\venv\Scripts\python.exe
Python Version: 	3.9.7
Python Path: 	
['C:\\Users\\Name\\codingprojects\\CoderMatching\\codermatching',
 'C:\\Program Files\\Python39\\python39.zip',
 'C:\\Program Files\\Python39\\DLLs',
 'C:\\Program Files\\Python39\\lib',
 'C:\\Program Files\\Python39',
 'C:\\Users\\Name\\codingprojects\\CoderMatching\\venv',
 'C:\\Users\\Name\\codingprojects\\CoderMatching\\venv\\lib\\site-packages']

The Template-loader postmortem looks like:

Django tried loading these templates, in this order:
Using engine django:
    django.template.loaders.filesystem.Loader: C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\forms\templates\codermatch\formSnippet.html (Source does not exist)
    django.template.loaders.app_directories.Loader: C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\contrib\admin\templates\codermatch\formSnippet.html (Source does not exist)
    django.template.loaders.app_directories.Loader: C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\contrib\auth\templates\codermatch\formSnippet.html (Source does not exist)

I don’t understand why Django trys to look up the template snippet in the venv.
Because for all the templates, which I specified in the render() methods of my views.py functions, Django looks for them in the project directories templates - hence they work as expected.

The debug windows specifies the Error during template rendering as follows:

In template C:\Users\Name\codingprojects\CoderMatching\codermatching\templates\codermatch\createAd.html, error at line 6

codermatch/formSnippet.html
1 	{% extends 'base.html' %}
2 	
3 	{% block main %}
4 	<form action="{% url 'codermatch:createAd' %}" method="post">
5 	    {% csrf_token %}
6 	    {{ form }}
7 	    <input type="submit" name="submit-ad" id="submit-ad" value="Create Ad">
8 	</form>
9 	{% endblock main %}

The error occurs with the following setup:

error setup

  • using the Reusable form templates of Django 3.2 (almost the same as working setup 2)

basic form (+ template_name property) in forms.py:

class createAdForm(forms.Form):
    template_name = "codermatch/formSnippet.html"

    projectTitle = forms.CharField(label='project title', max_length=100)
    creatorName = forms.CharField(label='creator name', max_length=50)
    projectDescription = forms.CharField(label='project description', max_length=1500)
    contactDetails = forms.CharField(label='contact details', max_length=300)
    projectStartDate = forms.DateField(label='project started (date)', widget=DateInput(attrs={'type': 'date'}), required=False)

views.py

def createAd(request):
    """
    This view method enables to publish own individual ads on the page.
    """
    # if POST request: process form data
    if request.method == 'POST':
        # create form instance + populate it with request data
        form = createAdForm(request.POST)
        # check if it's valid
        if form.is_valid():
            # process form.cleaned_data as required
            newAd = Ad(projectTitle=form.cleaned_data['projectTitle'],
                        creatorName=form.cleaned_data['creatorName'],
                        projectDescription=form.cleaned_data['projectDescription'],
                        contactDetails=form.cleaned_data['contactDetails'],
                        projectStartDate=form.cleaned_data['projectStartDate'])
            newAd.save()
            # redirect to new URL:
            return HttpResponseRedirect(reverse(viewname='codermatch:adDetail', args=(newAd.id,)))
    # if it's a GET request: show the unpopulated form
    elif request.method == 'GET':
        form = createAdForm()
    # if it's neither a POST, nor a GET request: send back to index page
    else:
        raise Http404('Only GET and POST requests are allowed')
    
    return render(request=request, template_name='codermatch/createAd.html', context={'form': form})

createAd.html - based on Reusable form templates (Django 4.0):

{% extends 'base.html' %}

{% block main %}
<form action="{% url 'codermatch:createAd' %}" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" name="submit-ad" id="submit-ad" value="Create Ad">
</form>
{% endblock main %}

formSnippet.html

{{ form.as_p }}

The form works without any errors in the following cases:

working setup 1

  • not using form snippets to display my form (hence no template_name property in my createAdForm)

basic form in a forms.py of app directory, like this (no template_name property):

class createAdForm(forms.Form):
    projectTitle = forms.CharField(label='project title', max_length=100)
    creatorName = forms.CharField(label='creator name', max_length=50)
    projectDescription = forms.CharField(label='project description', max_length=1500)
    contactDetails = forms.CharField(label='contact details', max_length=300)
    projectStartDate = forms.DateField(label='project started (date)', widget=DateInput(attrs={'type': 'date'}), required=False)

views.py is the same as in error setup (see above)

createAd.html

{% extends 'base.html' %}

{% block main %}
<form action="{% url 'codermatch:createAd' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}    # also working with {{ form }} only
    <input type="submit" name="submit-ad" id="submit-ad" value="Create Ad">
</form>
{% endblock main %}

working setup 2

  • using the Reusable form templates of Django 3.2 (instead of 4.0)

forms.py: same as in error setup (see above)

views.py: same as in error setup (see above)

createAd.html - based on Reusable form templates (Django 3.2):

{% extends 'base.html' %}

{% block main %}
<form action="{% url 'codermatch:createAd' %}" method="post">
    {% csrf_token %}
    {% include 'codermatch/formSnippet.html' %}
    <input type="submit" name="submit-ad" id="submit-ad" value="Create Ad">
</form>
{% endblock main %}

formSnippet.html: same as in error setup (see above)


general settings

In both working setups I’ve the following settings.py:

INSTALLED_APPS = [
    'codermatch', #this works because the name is defined in codermatch.apps.CodermatchConfig
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates' #alternative with same meaning: os.path.join(BASE_DIR, 'templates')
                ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Solution approaches:

  1. Error message misleading? Hence nonsense in formSnippet.html?

    • https://stackoverflow.com/a/43174094
    • This shouldn’t be the issue, since the Django 3.2 approach works with the formSnippet.html, right?
  2. app not correctly configured in INSTALLED-APPS

    • https://stackoverflow.com/a/63205788
    • I don’t think so :thinking:
  3. Wrong TEMPLATES setup in settings.py
    → I don’t think so :thinking:

Questions

  1. Why does Django look for the snippets in the venv, when using the Django 4.0 approach?
  2. Is this issue being caused by the changes in Django 4.0?

The template_name feature is added in Django 4.0. (See Working with forms | Django documentation | Django). That attribute has no meaning within Django 3.2 or earlier. It doesn’t throw an error because it’s not being used. (You can verify this by adding some text to that snippet file and seeing that it’s not being rendered in the form.)

I’m a little confused by what you’ve posted. Do you have two separate files named formSnippet.html?

What I have also discovered is that the form templates must be within the app_name/templates/ directory. So assuming a conventional directory structure, it should be in project_dir/codermatch/templates/codermatch/formSnippet.html (This is a resource for the form in codermatch, so it makes sense that it should be in the same location.)

With default settings, only app directories are searched for templates for form rendering. If any DIRS from the TEMPLATES setting are wanted to be included in the search, the FORM_RENDERER setting needs to be changed from the default of DjangoTemplates to TemplateSetting

https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-FORM_RENDERER

1 Like

Wow… way too much effort for such a simple solution :expressionless:
Thank you @KenWhitesell.

The formSnippet.html was in the templates of the project, not in the app templates.
I thought that I tested moving it to the app templates before and that it didn’t work that way.

However now it simply works after moving the formSnippet.html to project_dir/codermatch/templates/codermatch/formSnippet.html - as you say. :white_check_mark:

Thank you for the quick reply.


I know. That’s why I’ve suggested that there is a bug in Django 4.0.
Because I am using Django 4.0 and it didn’t work.

I assume you are confused because I’ve described three different approaches (two working setups and one which caused the error).

Or maybe you are as confused by the error message as I am.
Because the error message displays some confusing things:

On one hand Django is searching for templates in the venv - why?
And on the other hand Django pretends, that the error occurs in codermatch/formSnippet.html.
But the with the {{ form }} tag in lin 6 is actually the createAd.html (located in project_dir/templates/codermatch/createAd.html).
The createAd.html only includes {{ form.as_p }} (which should be a simple template for the {{ form }} in the /createAd.html
The createAd.html should use the formSnippet.html for form template rendering.


@kmtracey thank you for the explanation.
Then I am even more confused why Django is able to use the form template with the Django 3.2 approach - even if it’s located in project_dir/templates/codermatch/formSnippets.html.

The Django 3.2 approach is using pure old template mechanics which will rely on the TEMPLATES setting so will search project dirs (if specified there). It’s the 4.0 form rendering support that uses the FORM_RENDERER setting with a perhaps surprising default that does not use the base TEMPLATES setting.

The error message is trying to convey that codermatch/formSnippet.html can’t be found, not that there is any problem with its content. It’s not trying to indicate a problem in the specified template, it’s trying to say it cannot find the specified template.

Also: Django is searching for templates in the venv because there are apps in INSTALLED_APPS (django.forms and django.contrib.admin) with template directories to be searched.

2 Likes