from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == '__main__':
app.run(debug=True)
from flask_frozen import Freezer
from app import app, School
app.config['FREEZER_RELATIVE_URLS'] = True
app.config['FREEZER_DESTINATION'] = 'docs'
freezer = Freezer(app)
if __name__ == '__main__':
freezer.freeze()
These do NOT use any variables, but they do use layout.html
. You should probably fill layout.html
in with what we did in class, just steal it from the templates
folder.
show.html
{% extends 'layout.html' %}
{% block content %}
<h1>This is a thing</h1>
{% endblock %}
list.html
{% extends 'layout.html' %}
{% block content %}
<ul>
<li><a href="/something/A">Thing is something</a></li>
<li><a href="/something/B">Thing is something</a></li>
<li><a href="/something/C">Thing is something</a></li>
</ul>
{% endblock %}
layout.html
<p>This is your header</p>
{% block content %}{% endblock %}
<p>This is your footer</p>
- Start from the default Flask template up above, which you run with
python app.py
. It's a slightly edited version of the one from the Flask site. - Add your routes and pages with fake data. Your
html
goes intemplates/
. - Add bootstrap + a
layout.html
. Make sure you're using{% block content %}
and all of that! - Convert your CSV to SQLite
- Add SQLAlchemy + a data class to your app
- Use your SQLAlchemy model to replace the fake data
- Freeze your app with
python freezer.py
- Upload to GitHub
Make sure they all end in /
so they'll be frozen correctly!
Make sure all route endpoints have different function names. For example, if we have two def schools
we'll get an error. Function names actually don't matter, you can name when whatever you want, as long as they're different.
We did <a href="schools/{{ school.dbn }}/">
which is stupid and you should never do it. Flask can do cooler stuff now!
In our app.py
, we have a function called school
that wants a dbn
.
@app.route("/schools/<dbn>/")
def school(dbn):
school = School.query.filter_by(dbn=dbn).first()
return render_template("show.html", school=school)
In order to build a link to that spot from anywhere, we'll use the url_for
helper in our template.
<li><a href="{{ url_for('school', dbn=school.dbn) }}">{{ school.school_name }}</a></li>
Sorry I didn't talk about it in class! I'd somehow always avoided needing it.
It's nice easy with .to_sql
. You can check out the Jupyter Notebook.
- Name them appropriately (We had a
School
) - Dobule-check table name that
__tablename__
is pointing to - Make sure you have a primary key - it has to be a unique field. Ours was the column called
dbn
, so we useddbn = db.Column(db.String, primary_key=True)
. If your dataset didn't originally have one, saving from pandas to SQL probably added one calledindex
! If your key is an integer (likeindex
is) you'll want to usedb.Integer
instead ofdb.String
When we have a route like "/schools/<dbn>/"
we need to tell Frozen Flask what all possible values of dbn
might be. We do this with a URL generator, like this:
@freezer.register_generator
def school():
for school in School.query.all():
yield { 'dbn': school.dbn }
It grabs all of the schools and loops through the dbs. See the next error for a little more info!
Make sure the function names in freezer.py
match the names in app.py
. For example, in app.py
we have...
@app.route("/schools/<dbn>/")
def school(dbn):
school = School.query.filter_by(dbn=dbn).first()
return render_template("show.html", school=school)
...and then in freezer.py
we have...
@freezer.register_generator
def school():
for school in School.query.all():
yield { 'dbn': school.dbn }
Notice how the function names - def school
- and the variable name - dbn
- both match.
Every time you make a change, you need to freeze again and commit/push to GitHub again.
The biggest issue is making sure you're freezing in the right directory. If you have one directory where you're running your code and a different directory that's your GitHub repository, you're going to have to copy your code over again and again. You probably want to just have one place - the GitHub repo - where you're doing your freezing.
- Be sure to update the
href
values in your navbar - Maybe delete the search bar, too!
- Play around with Bootstrap. Look at all these components! All you have to do is cut and paste!
You can't search on a static site (well... not really), but you could always have "everything that starts with A" and "everything that starts with B," etc.
@app.route("/by_letter/<letter>/")
def by_letter(letter):
school = School.query.filter(School.school_name.startswith(letter)).all()
return render_template("list.html", schools=schools)
That will filter for every school name that starts with whatever is in the URL, and then you'd just freeze with A
, B
, C
, etc.
If you have two tables that are related to each other - for example, schools
with a lot of sat_scores
, and each school has dbn
and each sat score has a dbn
that says which school it's related to...
# A few extra imports
from sqlalchemy.orm import relationship
from sqlalchemy import ForeignKey
class School(db.Model):
__tablename__ = 'schools'
__table_args__ = {
'autoload': True,
'autoload_with': db.engine
}
dbn = db.Column(db.String, primary_key=True)
# this model has a relationship with the Score model
scores = db.relationship('Score')
class Score(db.Model):
__tablename__ = 'sat_scores'
__table_args__ = {
'autoload': True,
'autoload_with': db.engine
}
# You add ForeignKey(schools.dbn) when declaring a column
# to say that the dbn column you're talking about (dbn = )
# is connected to the dbn column in the schools table (schools.dbn)
dbn = db.Column(db.String, ForeignKey('schools.dbn'), primary_key=True)
Once you've done this, you could do something like
{% for score in school.sat_scores %}
<p>Writing score: {{ score.writing_mean }}</p>
{% endfor %}
To loop through every single score that a school has associated with it. It's like a JOIN
without writing all of the JOIN
stuff!