Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialAndy Hughes
8,479 Pointshow to utilise a form template in an admin template
Hi all,
New challenge for today! Not quite sure how to describe this but here goes!
I have created two tables to store driving standards and driving faults. For each table I've created a form template for inputting data. These can be accessed via their template urls.
However, I am trying to create an admin dashboard page that will also show these forms. I have tried adding the template into the admin template but it's giving me an "undefined form" error.
I've had a good look on stackoverflow and my current thinking is:
- I have not created the model correctly to allow the "StandardForm" to render inside the admin.html page.
- I'm using url_for where maybe I should be using include.
I've tried a whole bunch of different ways to try and get it showing the form, but always I get the error. Now asking if anyone can point me in the right direction. Code is below.
Traceback message
File "D:\Dropbox\Coding Projects\learner-driver-app\templates\admin.html", line 1, in top-level template code
{% extends 'layout.html' %}{% block content %}
File "D:\Dropbox\Coding Projects\learner-driver-app\templates\layout.html", line 70, in top-level template code
{% block content %}{% endblock %}
File "D:\Dropbox\Coding Projects\learner-driver-app\templates\admin.html", line 3, in block 'content'
{% include 'standards.html' %} {% endblock content %}Open an interactive python shell in this frame
File "D:\Dropbox\Coding Projects\learner-driver-app\templates\standards.html", line 1, in top-level template code
{% extends "layout.html" %} {% from 'macros.html' import render_field %} {% block content %}
File "D:\Dropbox\Coding Projects\learner-driver-app\templates\layout.html", line 70, in top-level template code
{% block content %}{% endblock %}
File "D:\Dropbox\Coding Projects\learner-driver-app\templates\standards.html", line 3, in block 'content'
{{ form.hidden_tag() }} {% for field in form %} {{ render_field(field) }} {% endfor %}
File "D:\business\python-development\anaconda3\Lib\site-packages\jinja2\environment.py", line 474, in getattr
return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'form' is undefined
admin.html
{% extends 'layout.html' %}{% block content %}
<h1>Welcome to the admin page!</h1>
{{ url_for('standards' )}} {% endblock content %}
app.py
@app.route('/add_standard', methods=('GET', 'POST'))
@login_required
def set_standard():
form = forms.StandardForm()
if form.validate_on_submit():
models.Standards.create_standard(
section=form.section.data,
standard=form.standard.data
)
flash("Standard created! Thanks!", "success")
return redirect(url_for('index'))
return render_template('standards.html', form=form)
standards.html
{% extends "layout.html" %} {% from 'macros.html' import render_field %} {% block content %}
<form method="POST" action="">
{{ form.hidden_tag() }} {% for field in form %} {{ render_field(field) }} {% endfor %}
<button type="submit" id="submit">Add Standard</button>
</form>
{% endblock %}
forms.py
class StandardForm(FlaskForm):
section = TextAreaField('Set section title', validators=[DataRequired()])
standard = TextAreaField('Enter the standard to be added', validators=[DataRequired()])
As I mentioned, it feels to me like it's something to do with the method in app.py. Maybe I need to create an additional method to handle the view for the admin template? Or maybe it's altering the existing method somehow.
Thanks in advance. :)
5 Answers
Andy Hughes
8,479 PointsFinally!! I figured it out!!
So the error was with the admin view in app.py. My code originally was as follows:
@app.route('/admin')
@login_required
def admin():
if (models.User.is_admin == True):
return render_template('admin.html')
else:
return redirect(url_for('index'))
After much naval gazing, pulling one's hair out, I figured out that when you inherit template views, any forms need to also be declared in the top level view. So for example, my admin.html was looking for a form from standards.html. But I needed to include the form declaration in the admin view function. So my code now looks as follows:
@app.route('/admin')
@login_required
def admin():
form = forms.StandardForm()
if (models.User.is_admin == True):
return render_template('admin.html', form=form)
else:
return redirect(url_for('index'))
With the addition of:
form = forms.StandardForm()
and
, form=form
It worked as expected. I tried it with and without one or other bits of the above code and it won't work unless both are present.
Thanks for the support Jeff Muday & jb30. Man that was a tough one!! :)
Jeff Muday
Treehouse Moderator 28,720 PointsIf you are using the Treehouse Workspaces, it has a rather old version of Flask-WTF installed (version 0.10.3). Unfortunately, a user can't upgrade Flask-WTF.
No worries-- you can change your import from FlaskForm
to just Form
, or you could alias it like I show below.
from flask_wtf import Form as FlaskForm
I hope this helps.
Jeff
Andy Hughes
8,479 PointsHi Jeff Muday, not entirely sure what you mean. I already have forms using FlaskForm which work fine and show up in their own templates as expected. I would just like to nest a template (containing a form) inside another template.
So in effect, the admin template would be a holding template for one or more other templates (containing forms).
The example I'll share is that I want admins who are logged in as admins to have access to the form to add driving standards and the form to add driving faults. Both of these have their own templates.
Are you suggesting that if I do the alias thing you mention, that would allow what I'm asking for?
Thanks in advance :)
Andy Hughes
8,479 PointsSo I've still not been able to fix this yet, which does not bode well for my coding skills. I am simply trying to take what I'm learning on the course and extend it for practice and proof I can walk on my own two feet. So, I could really use a helping hand.
In Kenneth's social app, there is a part where we check to see if the user is an admin. If they are, I have it redirecting to a basic admin page. Nothing special here, it's just a template.
But, in most admin systems, you get a dashboard and quick reference tasks that can be performed. This is what I am trying to play around with.
So in my admin.html template, I would like to show another template that allows an admin to add something to a database. For the sake of my example, I have called it standards.html, which contains a two field form. On the admin page, I would just like to show this standards form so that the admin can enter something.
The error I am getting is:
werkzeug.routing.BuildError: Could not build url for endpoint 'standards'. Did you mean 'set_standard' instead?
Now I'm thinking that this means, from my 'admin.html' code below...
{% extends 'layout.html' %} {% from 'macros.html' import render_field %} {% block content %}
<h1>Welcome to the admin page!</h1>
{{ url_for('standards') }}{% endblock content %}
...it doesn't know how to find or render the 'standards.html' file. I'm guessing that I'm either missing something in my code or I am using the wrong code to try and write in the standards file.
I have tried using:
{{ render_template('standards.html') }}
But then the error is 'render_template is not defined'.
I'm not necessarily after the answer, just a point in the right direction. It's making me feel pretty rubbish on what seems like it should be a simple problem.
Thanks in advance.
Jeff Muday
Treehouse Moderator 28,720 PointsThe Werkzeug error message is telling you to direct the actual Python name for the function in the code url_for("set_standard")
This is handy, because you could refactor your program and not have to change the template as long as your name for the routing function was still set_standard
You could also route "old school style" with a hardcoded URL as I show in the alternate route
Say your app had code like below...
@app.route('/standards')
def set_standard():
# code
# omitted
# for clarity
return render_template('standards.html', form=form)
Then your template would look something like this--
Extra HTML is added to make the links clickable, and stand these out in their own <div>
block.
{% extends 'layout.html' %}
{% from 'macros.html' import render_field %}
{% block content %}
<h1>Welcome to the admin page!</h1>
<div>
<a href="{{ url_for('set_standard') }}">Standards page</a>
</div>
<h2>Alternate routing "old school"</h2>
<div>
<a href="/standards">Standards page</a>
</div>
{% endblock content %}
Good luck with Python and Flask-- I have learned so much, and use Python frameworks all the time on my job!
Andy Hughes
8,479 PointsThanks as always Jeff Muday. I did try have a go at setting:
{{ url_for('set_standard') }}
This gets the app working again but of course, that just prints the url. Not what I want or need. I don't want to hard code the template if I can help it as it doesn't feel very Pythonic. :P
In effect, what I want to happen is that when the admin template loads, it 'includes' the standards.html template. At the moment, it just doesn't seem to be able to reach the template because something else might be stopping it. Not sure if there might be some sort of back and forth circular reference loop going on.
I have tried changing the 'extends' so that admin.html extends layout.html and standards.html extends admin.html but that seems to make no difference.
According to the documentation, child inheritance of templates is doing via 'extends'. I'm just not clever enough to figure it out. :P
I guess I could create a button and just link to the standards.html template but I really want a dashboard style page where other templates can be seen.
Jeff Muday
Treehouse Moderator 28,720 Points@AndyHughes If you have set it up in a TreeHouse workspace, you can post it as a Snapshot by clicking on the "suitcase" icon in the upper right-hand area of the workspace. Then post the link back to this thread.
I would be happy to take a look at it for you.
It is probably something simple that is holding you up.
Andy Hughes
8,479 PointsI appreciate the offer Jeff Muday. I don't have the code in workspaces. I'm coding in visual studio as I feel it's more up-to-date. I could potentially zip the folder up and send it to you? But I'm not sure if that would work based on Python versions, add-ins etc.
Can't believe how difficult this is proving, but I'm 100% sure it's the way my coding is written. Can't help but feel I've created some sort of circular reference that is preventing the template loading.
Andy Hughes
8,479 PointsAndy Hughes
8,479 PointsStill haven't managed to resolve this yet. Would be great if someone can help, or just point me in a direction to try and get a solution. Thanks