Subscribe for only $15 to access all of our content

#71: Simple CMS with Thesis

Elixir 1.7

Phoenix 1.3

Thesis 0.3.0

View source on GitHub


Sign up for our newsletter to get notified when new episodes drop

Thesis is a lightweight, but powerful, CMS for Elixir that was created by InfiniteRed.

While Thesis isn’t a fully featured CMS, it does a lot and is incredibly easy to drop in use where you need it. In this episode we’ll see how simple it is to add Thesis to a Phoenix application. Now let’s take a quick look at the application we’ll be using.

Our application here currently only has one page of content. And that’s a problem because we want this to be a blog. We’ll use Thesis in order to give us a way to easily add additional blog posts.

First let’s go to Hex and grab Thesis. Then we’ll open our Mixfile and add it to our list of dependencies.

mix.exs

...
defp deps do
  [
  ...
  {:thesis, "~> 0.3.0"},
  ...
  ]
end
...

Then we’ll download our new package. Once Thesis is downloaded we need to run the install script. This adds Thesis to our application as well as generates some database migrations. So let’s go ahead and migrate our database.

$ mix deps.get
...
$ mix thesis.install
Generated thesis app
*   creating lib/teacher/thesis_auth.ex
*   creating priv/repo/migrations/20180830010133_create_thesis_tables.exs
*   creating priv/repo/migrations/20180830010134_add_meta_to_thesis_page_contents.exs
*   creating priv/repo/migrations/20180830010135_add_indexes_to_tables.exs
*   creating priv/repo/migrations/20180830010136_add_template_and_redirect_url_to_thesis_pages.exs
*   creating priv/repo/migrations/20180830010137_change_content_default_for_page_content.exs
*   creating priv/repo/migrations/20180830010138_create_thesis_files_table.exs
*   creating priv/repo/migrations/20180830010139_create_thesis_backups_table.exs
*   creating priv/repo/migrations/20180830010140_change_and_convert_page_data_type_in_backups_table.exs
*   updating config/config.exs
*   updating web.ex
*       done Now run mix ecto.migrate to ensure your database is up to date.
$ mix ecto.migrate

With Thesis installed, we’ll need to update our layout to include the thesis_editor. We’ll open our app.html.eex and just after the body tag, we’ll add thesis_editor, passing in our connection.

Template path: lib/teacher_web/templates/layout/app.html.eex

...

<body>
<%= thesis_editor(@conn) %>
...

With that we can start up our server.

$ mix phx.server
[info] Running TeacherWeb.Endpoint with Cowboy using http://0.0.0.0:4000

And if we go back to the browser, we see a widget has loaded in the bottom right of the screen. This is provided by Thesis to edit the page. Let’s click on it - and we see we can edit the page settings - this is where we can edit different page properties like the title and the description.

Import, export, or restore content - here you can export and import content, which is useful in case you have different environments, for example moving something from staging into production. Thesis also keeps different versions of the page, which you can revert to here, publish changes, and discard changes.

But we can’t seem to edit the text on our page. That’s because in order to make a piece of content editable, we’ll need to wrap them in a content function. So let’s do that.

We’ll open our page’s index.html.eex and let’s wrap our header with the content function, giving it an identifier and we’ll specify “html” as the content type.

Then let’s do the same for our summary, wrapping it in the content function with an identifier and again specifying “html”.

Template path: lib/teacher_web/templates/page/index.html.eex

<%= content(@conn, "Homepage heading", :html) do %>
  <h2>The Record Blog</h2>
<% end %>

<div class="col-lg-6">
  <%= content(@conn, "Homepage summary", :html) do %>
    <p>Welcome to the Record Blog - where we talk about great music.</p>
  <% end %>
</div>

Then if we come back to the browser and edit the page we see our two content areas are now able to be edited. And they’re using the content from our page by default. Let’s go ahead and edit them.

And we have a simple HTML editor. Let’s make some edits to our page and once we have our text to where we want it, we can go ahead and “Publish Changes” - our changes are now saved.

Now, that we know how to edit the content that’s on a page, let’s see how we can add a page. Let’s open our config.exs and I’ll paste in our config to allow dynamic page creation with Thesis.

Our dynamic pages need a template to use. You can include a list of different templates to choose from here. Let’s include a template named “blog.html” that we’ll need to create.

config/config.exs

...

config :thesis, :dynamic_pages,
  view: TeacherWeb.PageView,
  templates: ["blog.html"],
  not_found_view: TeacherWeb.ErrorView,
  not_found_template: "404.html"

...

Then we’ll go ahead and create our “blog” templage in our “pages” directory. I’ll go ahead and paste in the structure of the template we’ll use. This is a simple template we can use for our blog posts. It has a sections for a title and body.

Template path: lib/teacher_web/templates/page/blog.html.eex

<div class="col-lg-4">
<%= content(@conn, "Blog title", :html) do %>
  <h2>Blog title goes here</h2>
<% end %>
</div>

<div class="col-lg-6">
  <%= content(@conn, "Blog post", :html) do %>
    <p>Blog post goes here</p>
  <% end %>
</div>

Now that we have our template, we’ll need to update our router to add a route for our dynamically created pages. We’ll open our router and we’ll include a new wildcard route. This should be the last route, otherwise it will always match. And we’ll have it be handled by our PageController with an action we’ll call “dynamic”.

lib/teacher_web/router.ex

...

get "/", PageController, :index
get "/*path", PageController, :dynamic

...

Now we need to open the PageController and add our dynamic action. Then inside it we’ll add the render_dynamic function provided by Thesis, passing in our connection. This is what Thesis uses to render our page from the database.

lib/teacher_web/controllers/page_controller.ex

...

def dynamic(conn, _params) do
  render_dynamic(conn)
end

...

Now we’ll need to restart our server since we made a change to our config.exs. And now when we go back to the browser and hover over the Thesis widget, we see an icon to add a new page.

Let’s go ahead and add our first page with Thesis. We’ll give it our path. Then there are fields for the page title and description. Then we can choose a template for our page. Since we only added one template we only see our “Blog” template displayed. If we had specified any other templates in the config, we would see them here.

We’ll click “apply” - and we see our page is created with the path we specified. Let’s go back to our homepage. And update the page to link to our new post. Great, we can now access our new blog post that was created with Thesis.

Now one thing our blog post could really use is an image. Fortunately Thesis lets supports image uploads. By default Thesis stores files in the database. One caveat to this is that if you’re storing a lot of images your database will balloon in size. As an alternative Thesis will let you easily use your own uploader. Your uploader just needs to be able to handle a %Plug.Upload{} struct, which you would then specify here for the uploader. We’ll stick with Thesis.RepoUploader for this app.

Then let’s go back to our blog template and we’ll add another content function. Giving it a name, “Blog image”, and then the “image” content type so Thesis knows that this is an image. Let’s also give it a placeholder image to use by default.

Template path: lib/teacher_web/templates/page/blog.html.eex

<%= content(@conn, "Blog Image", :image, alt: "Blog image",
do: "https://www.placehold.it/300x150") %>
...

Then let’s save our page and go back to the browser and our new image is displayed. Let’s edit our page and we’ll upload a new image to use. Then let’s update the content for our blog’s title and then its content.

This all looks good, so let’s publish our changes. Perfect, our page is updated and our image is being displayed. We’ve now got our site working well, but there’s one last thing. We don’t want everyone on the internet to be able to edit our site.

Let’s go back to our code. And when we ran $ mix thesis.install Thesis created a module named thesis_auth.ex. Let’s open it. Inside it there’s a single function named page_is_editable?. This is currently returning true, which allows our site to be edited.

One of the nice things about Thesis is that we can use any authentication strategy we want to determine who can edit our page. All we need to do to integrate it with Thesis is to return a boolean for page_is_editable?, in order to indicate whether the user can edit the page or not.

To get a better idea of how that would work, let’s take a look at the auth.ex module in our helpers directory. And we see there are two signed_in? functions. They simply check if there’s “auth” set to “true” in our params. If it is we return “true”. Otherwise we return “false”. While you wouldn’t do this in an actual application, I hope it gives a better idea of how easily you can incorporate your existing authentication logic into Thesis.

Let’s go back to thesis_auth.ex and update page_is_editable? to call Auth.signed_in?. Now for a page to be editable we’ll have to have “auth” true in our params.

lib/teacher/thesis_auth.ex

...

def page_is_editable?(conn) do
  TeacherWeb.Helpers.Auth.signed_in?(conn)
end

...

Then if we go back to the browser, we can’t edit our page. So let’s mock being signed in by adding “auth=true” to our query string. And great - our page is now editable.