-
Notifications
You must be signed in to change notification settings - Fork 30
Walkthrough
Before starting your first Statik project, you've got to understand that it isn't geared to only produce blogs, where the majority of other static site generators seem to be dedicated to blogs and the like.
After installing Statik, create yourself a directory somewhere for your project with the following sub-directory structure:
models/ - This is where your data models will go.
data/ - This is where your data will go.
templates/ - Your site templates will go here.
views/ - Configuration setting up your views (i.e. how your URLs/data/templates are processed) will go here.
assets/ - All of the static files (JavaScript/CSS/images/etc.) for your project will go here.
Fortunately, Statik provides a quickstart
command to create all of this for you:
> statik -p /path/to/new/project --quickstart
Create a config.yml
file in the root of your project, with the following (YAML) format:
project-name: My Project's Name
base-path: /
context:
static:
site-title: My Site Title
dynamic:
all-authors: session.query(Author).all()
This is purely for informational purposes.
Sometimes you'll want to serve your generated static site from a non-root location on your web server (e.g. http://somewhere.com/mysite/
instead of from http://somewhere.com
). Statik generates URLs in a relative manner, so this base-path
value is prepended to all generated URLs in your templates.
This variable defines static
and dynamic
context that is accessible from any of your templates (not just from specific views). The static
variables are provided as-is into your templates, whereas you define actual Python SQLAlchemy query code in the dynamic
variables (this is prepopulated before rendering the templates).
Statik uses SQLAlchemy under the hood to build up an in-memory SQLite database from which your views and templates can execute queries. You define your models in YAML format.
title: String
slug: String
author: Author # This will be auto-detected as a foreign key reference
summary: String
published: DateTime
content: Content
first-name: String
last-name: String
email: String
These will be converted into SQL database tables in your in-memory SQLite database while building your project.
IMPORTANT: All field names will have their dashes (-) replaced by underscores (_) when creating the in-memory database. So first-name
will become first_name
.
So now that you've defined what your database "tables" will be, you need some data in those tables to make your site useful. Data can either be defined in YAML format, or in Markdown format.
For each of your models, create a subfolder in your project's data/
folder as such:
data/Author/ - Where your site's "Author" model instances will be stored.
data/Post/ - Where your site's "Post" model instances will be stored.
Each .yml
or .md
file in each of those folders will represent an instance of the relevant model, with the file name of the file itself becoming the primary key of that particular instance. So, a file called michael.yml
in the data/Author
folder will be inserted as an entry whose primary key is michael
into the Author
in-memory database table.
By default, all primary key fields for all models are String
fields with the field name pk
.
first-name: Michael
last-name: Anderson
email: [email protected]
This defines an Author
model instance, where the pk
(primary key) field will be michael
(as per the file name, minus the extension), and the 3 fields of the model will be populated (first_name
, last_name
, email
).
---
title: My first post
slug: my-first-post
summary: A little summary about my first post
author: michael
published: 2016-06-22
---
This is the **Markdown** content that will automatically be inserted into the
`content` field of the model instance, because the `content` field is of type
`Content` (which, by the way, is translated into a SQLite `TEXT` field in the
in-memory database).
Note the YAML "preamble" at the beginning of the Markdown file, started and ended with three dashes (---
).
Also note how the author
field simply has the name michael
attached to it. Under the hood, this is the primary key reference to the relevant Author
model instance to attach to this Post
instance (i.e. this refers to our earlier defined michael.yml
instance).
Statik uses the Jinja2 templating engine under the hood, so Python developers with Jinja2 experience, and those familiar with the Django templating engine, shouldn't have a hard time writing Statik templates. For more detailed information, see the Jinja2 documentation.
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% if page_title %}{{ page_title }}{% else %}My Blog{% endif %}{% endblock %}</title>
</head>
<body>
<div class="container">
{% block body %}{% endblock %}
</div>
</body>
</html>
This defines our generic web page layout that we'll apply across all of our different kinds of pages. It allows us to avoid having to rewrite all of this code over and over again for all of our templates, only varying the parts that change depending on what kind of page we're displaying.
{% extends "base.html" %}
{% block body %}
<h1>Welcome to My Blog!</h1>
<p>These are my latest posts</p>
<ul>
{% for post in posts %}
<li><a href="{% url "post", post %}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}
This is a more complex template (to represent our home page/primary landing page), so let's go through it step by step:
-
{% extends "base.html" %}
- Tells the template engine to base our current template on thebase.html
file we defined previously. -
{% block body %}...{% endblock %}
- This tells the template engine to insert whatever's inside the block into the block namedbody
, which we defined in ourbase.html
file. -
{% for post in posts %}...{% endfor %}
- This is afor
loop that iterates over a list variable calledposts
, which we will define when we define our views (see below). -
{% url "post", post %}
- This tells the template engine to render a relative URL using the view calledpost
(which we will define a little later), passing the currentpost
object to that view when rendering the URL. -
{{ post.title }}
- Outputs the currentfor
loop iteration'spost
object'stitle
field value.
{% extends "base.html" %}
{% block body %}
<h1>{{ post.title }}</h1>
<div class="content">
{{ post.content|safe }}
</div>
{% endblock %}
This template defines how we'll be rendering individual posts, and is similar in structure to our home.html
template in how it overrides the base.html
page, but defines different inner content inside the body
block.
You'll notice that we're passing the post.content
variable contents through the safe
filter here - this is because our Markdown content is generated as pure HTML, and the Jinja2 templating engine automatically escapes HTML content by default. We're telling Jinja2 here that our post.content
field's contents are safe to render without escaping here.
Views allow you to tell Statik how to render the various different URLs of your static web site. They can either be simple, or complex. Simple views specify how to render a specific URL (e.g. if you're rendering the home page, or an about page), whereas complex views define recipes for collections of views (e.g. if you're rendering a whole bunch of Post
instances).
path: /
template: home # The .html is automatically added by Statik
context:
static:
page-title: Welcome to my blog
dynamic:
posts: session.query(Post).filter(Post.published != None).order_by(Post.published.desc()).all()
There's a lot going on here in one little view, so let's walk through it:
-
path: /
- This defines which relative URL (relative to the base path) we're rendering. As you can see, this is a simple view, as it only defines a single path. -
template: home
- This defines which template to use in ourtemplates
folder when rendering this view. Statik will automatically add the.html
extension when looking for the template in yourtemplates
folder, so this refers to ourtemplates/home.html
template that we defined in our previous step. -
context:
- This section defines "context" variables which are passed into the Jinja2 template renderer. -
static:
- This defines "static" context variables passed in as-is into the Jinja2 template rendered. If a variable is defined under this section, like thepage-title
variable, its value will be passed into the template engine as-is. -
dynamic:
- This is where some real magic happens. Variables defined as being "dynamic" are assumed to be SQLAlchemy ORM queries, and are therefore actually just Python code that's executed to populate these dynamic variables. Statik makes asession
variable available, which is an ORM session to access the in-memory SQLite database once all models and data have been populated. You can then refer to each of your defined models by their name, as they are also made available when defining your queries.
path:
template: /{{ post.published|date("%Y/%m/%d") }}/{{ post.slug }}
for-each:
post: session.query(Post).filter(Post.published != None).all()
template: post
This is an example of a complex view, where a recipe for a path is given.
-
path: template:
- This defines the template for the URL we're going to render. It's just a Jinja2 template. -
path: for-each:
- We can define one variable here (we've defined one calledpost
), where its value is a SQLAlchemy ORM query. Here we're basically getting a list of all of the posts that have been published already. Statik will render this view for each of these instances, and will render each one according to the template defined intemplate:
above. -
template:
- This is the template for the view itself, or how we're going to render each post. Here, we've specified to use thetemplates/post.html
file to render each post.
NOTE: Remember how, in our templates/home.html
file we used the {% url %}
tag to refer to the post
view? Well, this is the view we're referring to (views/post.yml
). Statik will take the post
instance from the home.html
template and pass it into this views/post.yml
view in order to compute the post's URL from the view recipe.
Now, we're ready to build the project.
> statik
Now check your project's directory for a public/
folder - that's where we've generated the new static site.