I am trying to use a wtf_form in python and flask to have a user enter a date and time and then save it to Sqlalchemy. I will share what I have, but right now my flask form once populated won't accept the date entries, when I submit the form. It doesn't give me any errors, so I am sure it is a mismatch. Any thoughts are much appreciated. Thank you so much for any help. I am presently using SQLite as my database.
class Events(db.Model):
__tablename__ = 'events'
id = db.Column(db.Integer, primary_key=True)
eventname = db.Column(db.Text(64))
eventstart = db.Column(db.DateTime, nullable=False)
eventstop = db.Column(db.DateTime, nullable=False)
def __init__(
self, eventname, eventstart, eventstop
):
self.eventname = eventname
self.eventstart = eventstart
self.eventstop = eventstop
My forms.py is:
# /myproject/events
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, IntegerField
from wtforms.validators import DataRequired
from wtforms.fields import DateField
# Building the form in order to get a date picker and save it to the database.
class RegistrationForm(FlaskForm):
eventname = StringField('Event Name', validators=[DataRequired()])
eventstart = DateField(
'Enter start date and time', id='datepick', validators=[DataRequired()]
)
eventstop = DateField(
'Enter end date and time', id='datepick', validators=[DataRequired()]
)
submit = SubmitField('Register!')
My view.py is:
from myproject import app, db
from flask import (
render_template, redirect, url_for, flash,
Blueprint, request
)
from flask_login import login_user, login_required, logout_user
from myproject.models import Events
from myproject.events.forms import RegistrationForm
import validators
from flask_bootstrap import Bootstrap
Bootstrap(app)
events_blueprint = Blueprint(
'events', __name__,
template_folder='templates/events'
)
# #login_required
#events_blueprint.route('/add', methods=['GET', 'POST'])
def register():
try:
form = RegistrationForm()
if form.validate_on_submit():
event = Events(
eventname=form.eventname.data,
eventstart=form.eventstart.data,
eventstop=form.eventstop.data,
)
db.session.add(event)
db.session.commit()
flash("Thank you for registering your event.")
return redirect(url_for('list_event'))
return render_template('add.html', form=form)
except Exception as e:
print(e)
Lastly, my HTML is:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}This is an example page{% endblock %}
{% block head %}
{{ super() }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css">
{% endblock %}
{% block content %}
<div class="container">
<h1>Hello, Bootstrap</h1>
<div class="row">
<div class='col-sm-6'>
<form method="POST">
{{form.hidden_tag()}}
{{form.eventname.label}}{{form.eventname()}}<br><br>
{{form.eventstart.label}}{{form.eventstart()}}<br><br>
{{form.eventstop.label}}{{form.eventstop()}}<br><br>
{{form.submit()}}
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript">
$(function () {
$('#datepick').datetimepicker();
});
</script>
{% endblock %}
The issue is when you're saving the form information into your database. You've correctly declared your event start and event stop as Datetime objects in your models.py and you've also correctly used the DateField input with Flask-WTF so everything is going to look correct on the front end but when you get a date from the form, it comes back as a string and it needs to be parsed and formatted as a date. Luckily there's a way to do that!
Add the following import to your view.py file and make these edits for creating the event in your database.
from datetime import datetime
...
eventstart=datetime.strptime(form.eventstart.data, '%Y-%m-%d')
eventstop=datetime.strptime(form.eventstop.data, '%Y-%m-%d')
That should fix it! Hope this is helpful.
Related
I am using generic MethodViews in my Flask application together with Flask-MongoEngine. I am also trying to automate form creation based on the given model by using the model_form(db_model) function. Lastly, I am trying to let Bootstrap-Flask take care of rendering the produced form. The problem is that there is no submit button, because the form returned by model_form does not include a SubmitField.
Is there a way to append a submit field before the form is rendered?
Here is a minimally viable app
from datetime import datetime
from flask import Flask, Blueprint, render_template, url_for
from flask_mongoengine import MongoEngine
from flask_bootstrap import Bootstrap5
db = MongoEngine()
bootstrap = Bootstrap5()
class Role(db.Document):
meta = { 'collection': 'roles' }
name = db.StringField(verbose_name='Role Name', max_length=24, required=True, unique=True)
description = db.StringField(verbose_name='Role Description', max_length=64, default=None)
date_created = db.DateTimeField(default=datetime.utcnow())
date_modified = db.DateTimeField(default=datetime.utcnow())
def get_id(self):
return str(self.id)
def safe_delete(self):
if self.name == 'admin':
return False, 'Admin role is not removable.'
result, resp = self.delete()
if not result:
return result, resp
return True, f'Privilege {self.name} removed successfully'
class AuthItemView(MethodView):
def __init__(self, model, template):
self.model = model
self.template = template
def _get_item(self, id):
return self.model.objects.get_or_404(id=id)
def get(self, id):
item = self._get_item(id)
return render_template(self.template, item=item)
class AuthItemEditor(AuthItemView):
def _get_form(self):
form = model_form(self.model, exclude=['date_created', 'date_modified'])
return form()
def get(self, id):
item = self._get_item(id)
form = self._get_form()
return render_template(self.template, item=item, form=form)
def post(self, id):
return 'Yay! Role changes were posted!', 200
staff_bp = Blueprint('staff', __name__)
staff_bp,add_url_rule('/roles/<id>', view_func=AuthItemView.as_view('role_info', Role, 'roleinfo.html')
staff_bp.add_url_rule('/roles/<id>/edit', view_func=AuthItemEditor.as_view('role_edit', Role, 'roleedit.html')
def create_app():
app = Flask(__name__)
app.config['MONGODB_SETTINGS'] = {
'db': 'someapp',
'host': 'localhost',
'port': 27017
}
app.config['SECRET_KEY'] = 'some-very-secret-key'
app.register_blueprint(staff_bp)
db.init_app(app)
bootstrap.init_app(app)
return app
if __name__ == __main__:
a = create_app()
a.run()
The roleedit template is where the magic has todoesn't happen:
{% extends 'base.html' %}
{% from 'bootstrap5/form.html' import render_form %}
{% block content %}
<h1>Edit Properties of <em>{{ item.name }}</em></h1>
<p>
{{ render_form(form) }}
<!-- There is no submit button!!! How do I submit??? //-->
</p>
{% endblock %}
Here is a very basic base template:
<!doctype html>
<html lang="en">
<head>
{% block head %}
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block styles %}
<!-- Bootstrap CSS -->
{{ bootstrap.load_css() }}
{% endblock %}
<title>Page title</title>
{% endblock %}
</head>
<body>
<!-- Your page content -->
{% block content %}{% endblock %}
{% block scripts %}
<!-- Optional JavaScript -->
{{ bootstrap.load_js() }}
{% endblock %}
</body>
</html>
Lastly, the roleinfo template (it's there just to be able to run the app):
{% extends 'base.html' %}
{% block content %}
<h1>Properties of <em>{{ item.name }}</em></h1>
<!-- display item properties as a table or as responsive bootstrap grid //-->
{% endblock %}
You can pass a Form class to model_form. For example:
class BaseForm(Form):
submit = SubmitField('Submit')
class AuthItemEditor(AuthItemView):
def _get_form(self):
form = model_form(self.model, base_class=BaseForm, exclude=['date_created', 'date_modified'])
return form()
# ...
Turns out, it is rather easy...
# ... other imports ...
from wtforms import SubmitField
# ... inside the get_form function
form = model_form(self.model)
setattr(form, 'submit', SubmitField('Submit'))
# now return the fixed form
If anyone has a different approach, please share!
Good day
I'm struggling to find a way to check in jinja2 if a wtform check box is checked.
I want to be able to make an if statement that shows when a check box is check that it will display additional input fields like:
{% if form.BooleanField == checked %}
display fields
{% endif %}
To enable one or more form fields you can use the disabled attribute of an input field or field set. Using a style sheet rule, the respective element is displayed or not.
When the user clicks the checkbox, an event listener updates the disabled attribute based on the state of the box.
An additional validator is required to validate the optional fields on the server side as well. This expects an entry in the respective field as long as the value of the referenced form field has been set.
Below is the complete code for a sample application.
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm
from wtforms import (
BooleanField,
StringField,
SubmitField
)
from wtforms.validators import (
DataRequired,
Optional
)
app = Flask(__name__)
app.secret_key = 'your secret here'
class RequiredIf(DataRequired):
field_flags = ('requiredif',)
def __init__(self, other_field_name, message=None, *args, **kwargs):
self.other_field_name = other_field_name
self.message = message
def __call__(self, form, field):
other_field = form[self.other_field_name]
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
if bool(other_field.data):
super(RequiredIf, self).__call__(form, field)
class ExampleForm(FlaskForm):
enabled = BooleanField(validators=[Optional()])
field1 = StringField(validators=[RequiredIf('enabled')])
field2 = StringField(validators=[RequiredIf('enabled')])
submit = SubmitField()
#app.route('/', methods=['GET', 'POST'])
def index():
form = ExampleForm(request.form)
if form.validate_on_submit():
for f in form:
print(f'{f.name}: {f.data}')
return render_template('index.html', **locals())
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
<style>
fieldset[disabled], input[disabled] {
display: none;
}
</style>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
<div>
{{ form.enabled }}
{{ form.enabled.label() }}
</div>
<fieldset name="optional-fields" {% if not form.enabled.data %}disabled{% endif %}>
<div>
{{ form.field1.label() }}
{{ form.field1 }}
</div>
<div>
{{ form.field2.label() }}
{{ form.field2 }}
</div>
</fieldset>
{{ form.submit }}
</form>
<script type="text/javascript">
(function() {
const enabledField = document.getElementById('enabled');
const optionalFieldset = document.querySelector('fieldset[name="optional-fields"]');
enabledField.addEventListener('change', function(event) {
optionalFieldset.disabled = !this.checked;
});
})();
</script>
</body>
</html>
I am new to Flask and I am trying to save form data from a Flask form to database using SQLAlchemy and I am not having any luck. I have tried several methods from research I found both here and outside of this forum.
When I take the simplistic route, the web form works and I can enter data but it will not populate the DB.
----Models----
class QIDMapping(db.Model):
id = db.Column(db.Integer, primary_key=True)
qid_number = db.Column(db.Integer)
br_field_name = db.Column(db.String(75))
vendor_field = db.Column(db.String(75))
----Forms----
class QIDForm(Form):
qidnumber = IntegerField('qidnumber', validators=[DataRequired()])
brfieldname = StringField('brfieldname', validators=[DataRequired()])
vendorfieldname = StringField('vendorfieldname')
----Views----
from flask import render_template, flash, redirect, session, url_for,
request, g
from flask_wtf import form
from app import app, db
from .forms import QIDForm
from .models import User, QIDMapping
from flask.ext.sqlalchemy import SQLAlchemy
#app.route('/qidmapping', methods=['GET', 'POST'])
def qid_map_update():
form = QIDForm()
return render_template('qidmapping.html',
title='QID Mapping',
form=form)
----qidmapping.html----
{% block content %}
<h1>Map QIDs to Vendor File</h1>
<form action="" method="POST">
{{form.hidden_tag()}}
<p>
Please enter the QID, BrassRing Field Name and Vendor Tag
<br>
<h2>QID Number {{ form.qidnumber(size=25) }}<br></h2>
<h2>BR Field {{ form.brfieldname(size=25) }}<br></h2>
<h2>Vendor Field {{ form.vendorfieldname(size=25) }}<br></h2>
</p>
<p><br>
</p>
<p><input type="submit" value="Save Fields">
</p>
</form>
{% endblock %}
I have also tried the method in this post Flask - WTForm - save form to db
and when I do, I get a Method Not Allowed error and I'm not sure why.
----view for question 20837209 format----
#app.route('/qidmapping', methods=['GET', 'POST'])
def qid_map_update():
form = QIDForm()
if form.validate_on_submit():
newform = (
form.qidnumber.data,
form.brfieldname.data,
form.vendorfieldname.data
)
db.session.add(newform)
db.session.commit()
return redirect('/qidmapping')
return render_template('qidmapping.html',
title='QID Mapping',
form=form)
Any help would be greatly appreciated!
Try replacing
newform = (
form.qidnumber.data,
form.brfieldname.data,
form.vendorfieldname.data
)
db.session.add(newform)
with
m = QIDMapping()
m.qid_number = form.qidnumber.data
m.br_field_name = form.brfieldname.data
m.vendor_field = form.vendorfieldname.data
db.session.add(m)
... and if that doesn't work. Do your standard POST troubleshooting:
1) Verify POST request
2) Ensure CSRF is working correctly.
3) Log validation errors / success
4) Check for DB exceptions
I'm trying to implement Select2 field in one of my flask views. Basically I want the same select2 field in my flask application view (not a flask admin modelview) as in Flask-admin model create views. Currently my solution has been QuerySelectField from wtforms that looks something like this
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all())
This allows me to load and select all the data I need, but it does not provide select2 search box etc. Currently all that I have found is Select2Field and Select2Widget from flask/admin/form/fields and flask/admin/form/widgets similarly like in this post https://stackoverflow.com/questions/24644960/how-to-steal-flask-admin-tag-form-field and also select2 documentation at http://ivaynberg.github.io/select2/
As I understand these could be reusable, meaning there is no need for other custom widgets, custom fields.
Would be grateful if someone could provide more information about select2 field implementation in flask application (including views, templates, forms files and how to correctly "connect" with neccessary js and css files, also how to load the field up with the database model I need).
This worked for me:
...
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from flask_admin.form.widgets import Select2Widget
...
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all(),
widget=Select2Widget())
And in your template:
{% extends "admin/master.html" %}
{% import 'admin/lib.html' as lib with context %}
{% block head %}
{{ super() }}
{{ lib.form_css() }}
{% endblock %}
{% block body %}
...
{% endblock %}
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
{% endblock %}
I can try to put together a minimal working example if necessary.
I had a similar requirement and have put together a minimal example.
Note the following:
Class TestView defines three routes; a get view, a post view and an Ajax lookup view.
Function get_loader_by_name takes a string name and returns a QueryAjaxModelLoader. This function is used in both the Ajax lookup call and in the TestForm field definitions.
The text displayed in the Select2 widgets is the value returned by the User model's __unicode__ method.
I've used Faker to generate the sample user data.
File app.py:
from flask import Flask, render_template, url_for, request, abort, json, Response
from flask.ext.admin import Admin, BaseView, expose, babel
from flask.ext.admin.contrib.sqla.ajax import QueryAjaxModelLoader
from flask.ext.admin.model.fields import AjaxSelectField, AjaxSelectMultipleField
from flask.ext.sqlalchemy import SQLAlchemy
from wtforms import Form
from faker import Factory
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
try:
from flask_debugtoolbar import DebugToolbarExtension
DebugToolbarExtension(app)
except:
pass
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Unicode(length=255), nullable=False)
last_name = db.Column(db.Unicode(length=255), nullable=False)
email = db.Column(db.Unicode(length=254), nullable=False, unique=True)
def __unicode__(self):
return u"{first} {last}; {email}".format(first=self.first_name, last=self.last_name, email=self.email)
def get_loader_by_name(name):
_dicts = {
'user': QueryAjaxModelLoader(
'user',
db.session, User,
fields=['first_name', 'last_name', 'email'],
page_size=10,
placeholder="Select a user"
)
}
return _dicts.get(name, None)
class TestView(BaseView):
def __init__(self, name=None, category=None,
endpoint=None, url=None,
template='admin/index.html',
menu_class_name=None,
menu_icon_type=None,
menu_icon_value=None):
super(TestView, self).__init__(name or babel.lazy_gettext('Home'),
category,
endpoint or 'admin',
url or '/admin',
'static',
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._template = template
#expose('/', methods=('GET',))
def index_view(self):
_form = TestForm()
return self.render(self._template, form=_form)
#expose('/', methods=('POST',))
def post_view(self):
pass
#expose('/ajax/lookup/')
def ajax_lookup(self):
name = request.args.get('name')
query = request.args.get('query')
offset = request.args.get('offset', type=int)
limit = request.args.get('limit', 10, type=int)
loader = get_loader_by_name(name)
if not loader:
abort(404)
data = [loader.format(m) for m in loader.get_list(query, offset, limit)]
return Response(json.dumps(data), mimetype='application/json')
# Create admin and Test View
admin = Admin(app, name='Admin', template_mode='bootstrap3')
admin.add_view(TestView(template='test.html', name="Test", url='/test', endpoint='test'))
class TestForm(Form):
single_user = AjaxSelectField(
loader=get_loader_by_name('user')
)
multiple_users = AjaxSelectMultipleField(
loader=get_loader_by_name('user')
)
#app.route('/')
def index():
return render_template("index.html", link=url_for('test.index_view'))
def build_db():
db.drop_all()
db.create_all()
fake = Factory.create()
for index in range(0, 1000):
_first_name = fake.first_name()
_last_name = fake.last_name()
_user_db = User(
first_name=_first_name,
last_name=_last_name,
email="{first}.{last}{index}#example.com".format(first=_first_name.lower(), last=_last_name.lower(), index=index)
)
db.session.add(_user_db)
db.session.commit()
#app.before_first_request
def before_first_request():
build_db()
if __name__ == '__main__':
app.debug = True
app.run(port=5000, debug=True)
File templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
</head>
<body>
Go to the test form
</body>
</html>
File templates/test.html:
{% import 'admin/static.html' as admin_static with context %}
{% import 'admin/lib.html' as lib with context %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap-theme.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='admin/css/bootstrap3/admin.css') }}" rel="stylesheet">
{{ lib.form_css() }}
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-2">
<form>
{{ lib.render_form_fields(form) }}
</form>
</div>
</div>
</div>
<script src="{{ admin_static.url(filename='vendor/jquery-2.1.1.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap3/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/moment-2.8.4.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js') }}" type="text/javascript"></script>
{{ lib.form_js() }}
</body>
</html>
Update July 2018
Added a standalone Flask extension available on Github - Flask-Select2 - WIP.
I recently implemented a "tags" field in the front-end of a Flask app, using Select2 and WTForms. I wrote a sample app, demonstrating how I got it working (the view code for populating the select options, and for dynamically saving new options, is where most of the work happens):
https://github.com/Jaza/flasktaggingtest
You can see a demo of this app at:
https://flasktaggingtest.herokuapp.com/
No AJAX autocomplete in my sample (it just populates the select field with all available tags, when initializing the form). Other than that, should be everything that you'd generally want for a tagging widget in your Flask views / templates.
Other answers seem not to work anymore. What I did to make it work is:
On the backend:
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
...
users = users = QuerySelectMultipleField('users', query_factory=lambda: User.query.order_by(User.fullname).all(),
get_label=lambda u: u.fullname, get_pk=lambda u: u.id)
Then in the frontend:
$("#users").attr("multiple", "multiple").select2();
You need to manually add css and js for select2.
I am trying to create a contact form using flask but keep getting this error when the page is rendered.
'forms.ContactForm object' has no attribute 'hidden_tag'
Here are my files:
contact.html
{% extends "layout.html" %}
{% block content %}
<h2>Contact</h2>
<form action="{{ url_for('contact') }}" method=post>
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name }}
{{ form.email.label }}
{{ form.email }}
{{ form.subject.label }}
{{ form.subject }}
{{ form.message.label }}
{{ form.message }}
{{ form.submit }}
</form>
{% endblock %}
forms.py
from flask.ext.wtf import Form
from wtforms import Form, TextField, TextAreaField, SubmitField, validators
class ContactForm(Form):
name = TextField("Name", [validators.Required()])
email = TextField("Email",[validators.Required(), validators.email()])
subject = TextField("Subject", [validators.Required()])
message = TextAreaField("Message", [validators.Required()])
submit = SubmitField("Send")
routes.py
from flask import Flask, render_template, request
from forms import ContactForm
app = Flask(__name__)
app.secret_key = 'development key'
#app.route('/')
def home():
return render_template('home.html')
#app.route('/about')
def about():
return render_template('about.html')
#app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if request.method == 'POST':
return 'Form posted.'
elif request.method == 'GET':
return render_template('contact.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
All the other page templates are working perfectly fine. Any advice would be awesome! Thanks for the help!
I just fixed this problem as well.
Your problem is that you imported Form twice, rendering your flask-wtf Form import useless.
from flask_wtf import Form
from wtforms import Form, TextField, TextAreaField, SubmitField, validators
# ^^^ Remove
Only the flask-wtf extension has the special Form class which can handle CSRF automatically / other stuff.
I tried to fix this, too.
After removing brackets "()" appended after hidden_tag, it works.
{{ form.hidden_tag }}
It took me some time to fix this.
First import Form, fields, bootstrap as:
from flask_wtf import Form
from wtforms import StringField #etc
from flask_bootstrap import Bootstrap
Config secret key and bootstrap
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key'
Bootstrap(app)
create your form as your used to:
class ContactForm(Form):
name = TextField("Name", [validators.Required()])
email = TextField("Email",[validators.Required(), validators.email()])
subject = TextField("Subject", [validators.Required()])
message = TextAreaField("Message", [validators.Required()])
submit = SubmitField("Send")
Nothing special in the routing aswell, just return it normaly.
In the html:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% if form %}
{{ wtf.quick_form(form, ) }}
{% endif %}
And that's it. Hope you find some (or all) of it useful.
Update for #Yuji'Tomita'Tomita answer :
You should import FlaskForm instead of Form
from flask_wtf import FlaskForm
from wtforms import TextField, TextAreaField, SubmitField, validators
The error that you're seeing is telling you that forms.ContactForm has no method called "hidden_tag". You're referencing that method on the 6th line of contact.html like this:
{{ form.hidden_tag() }}
According to the flask documentation, this is the correct way to implement CSRF protection.
I would start by removing the line that references "form.hidden_tag()", then see if your form works. Then go back and implement CSRF protection per those instructions from the documentation.