Features

Responsive Images

Responsive images adapt to different screen sizes, ensuring optimal display across devices. This project uses two types of responsive images:

  • Normal Images: Displayed within page content.

  • Thumbnail Images: Shown as thumbnails in content and enlarged in a modal upon clicking.

Image Renditions: For each image, three renditions are created in AVIF and JPEG formats: - 1x Width: Standard size, used in the src attribute of the img tag. - 2x Width: Double size, used provided the image is not larger or nearly the same size as the original. - 3x Width: Triple size, used only when necessary. These renditions are specified in the srcset attribute of the source or img elements.

Those three renditions are put into the srcset attribute of the related source or img elements.

Normal Images

Normal images fill a slot with a width of 1110px and a maximum height of 740px. But you can configure those values in the settings.

Therefore, if you have an image which is quadratic and has a width of 3000px it will rendered with a maximum width of 740px, but delivered with a width of 2220px to high pixel density devices.

Both AVIF and JPEG formats are supported.

Thumbnail Images

Thumbnail images have a width of 120px and a maximum height of 80px. But you can also set those values in the settings.

They also support AVIF and JPEG formats.

Frontend

Pagination

The blog index page comes with pagination support. You can set the number of posts per page using the POST_LIST_PAGINATION setting.

If there are more then 3 pages, there will be a “…” in the pagination. If there are more then 10 pages, there will be two “…” in the pagination.

Django-Admin

The file sizes of an audio object are cached automatically. But for old audio objects there’s an admin action where you can update the file size cache for all associated audio files.

Show the admin action to update the file size cache

Social Media

Twitter

If you share a link to a detail page of an episode, there will be a Twitter Player Card added to your tweet. At the moment you have to generate the metadata manually. For an example have a look at this episode template.

Image of a Twitter Card

Comments

You can enable / disable comments on app, blog and post-level. For app-level, there’s a global switch you can use in the settings. Blog and post models have a comments_enabled database field. They are set to True by default.

Caveats

The ajax-calls django-fluent-comments does depend on the availability of a full jquery version.

Comment Spam Filter

There’s a simple Naive Bayes-based spam filter for comments built in. It’s not very smart, but it’s good enough to filter out most spam. It’s also very easy to train and very fast to run. And it’s only slightly above one hundred lines of pure Python code.

A comment is considered ham if it’s public and not removed. All other comments are considered spam. It’s possible to re-train the spam filter via a Django Admin action on the SpamFilter model. The precision, recall and F1 performance indicators are also shown in the admin interface.

Show a spam filter row in the Django admin interface

Blog

Title

The title field is used to populate the title attribute in the feed. And it is also used in the default templates to display the title of the blog on the blog index page.

Subtitle

The subtitle field is used to populate the subtitle attribute on the podlove player.

Description

The description field is used to populate the description attribute in the feed. And it is also used in the default templates to display a short description of the blog on the blog index page.

Email

Contact address for the blog.

Author

If you set the custom CharField field named author on a Blog-Page using the Wagtail or Django-Admin interface, the content of this field is then used to populate following attributes in the feed:

  • itunes:author

  • itunes:name

  • author_name in atom feed

Template Base Dir

The template_base_dir field is used to specify the base directory for the templates used to render the blog. It’s basically a theme switcher.

Cover Image

The cover_image field is used to specify the cover image for the blog. If posts have no cover image, the blog cover image will be used as a fallback. There’s a cover_alt_text field to specify the alt text for the cover image.

Promote > Title

This title is show in search engine results linking to the blog.

Promote > Description

This description is show in search engine results linking to the blog.

Promote > NoIndex

If you set the custom BooleanField field named noindex on a Blog-Page using the Wagtail or Django-Admin interface, the Page and all its subpages will be excluded from being indexed by search engines.

Post

Posts are Wagtail pages that have a blog page as a parent.

Visible Date

You can set the visible_date of a post, which will be showed in templates and used for sorting posts shown on the blog index page.

Cover Image

The cover image for a post is used for meta tags like twitter:image or og:image. But you can also use it in the templates for the blog index page or the post detail page. If you set cover_alt_text, it can be used as the alt attribute of the cover image.

For posts without a cover image, the blog’s cover image will be used. Alternatively, I often generate one using the shot-scraper tool:

pipx install shot-scraper
shot-scraper install  # installs chromium headless
shot-scraper shot https://wersdoerfer.de/blogs/ephes_blog/ -w 800 -h 400 --retina --quality 60

This will generate a screenshot of the blog post and save it as a jpeg. But you can also use png. It is also possible to set a separate alt text for the cover image.

Tags

You can add tags to a post. Tags can be used to filter posts on the blog index page.

Promote > Title

There is a field called title in the promote tab of the Wagtail admin which is used to set the title of the post for search engine results as the clickable headline. This will also be used for the og:title and twitter:title meta tags.

Promote > Description

The description field in the promote tab of the Wagtail admin is used to set the description of the post for search engine results. This will also be used for the og:description and twitter:description meta tags.

Body

The body of a post is a StreamField. You can add different types of blocks to the body of a post. There are two types of blocks you can add to the body of a post:

1. Overview

Overview blocks are used to display a summary of the post on the blog index page or in feeds.

2. Detail

Detail blocks are used to display the full content of the post. Usually it is shown on the post detail page.

Types of Blocks

Inside the Overview and Detail blocks following types of blocks are available:

  • Heading

  • Paragraph - Rich Text which can include headings, images, links, etc.

  • Image - A single image

  • Gallery with Layout - A gallery of images with different layout options

  • Embed - Embed a video or other content from a URL

  • Video

  • Audio - Displayed using the Podlove Web Player

  • Code - Code block with syntax highlighting

Podcast

Podcasts are blogs that have some additional features to better support podcasting.

There are some additional fields that are available for podcasts:

Itunes Artwork

The image that will be used in the podcast feed as the iTunes artwork.

Episode

Podcast episodes are Wagtail pages that have a podcast page as a parent. They have the same features as blog posts, but with some additional fields for better podcast support.

Cover Image

In addition to the effect setting a cover image for a post has, setting a cover image for an episode will also be used as the episode’s artwork in the podcast feed and in the Podlove Web Player.

If no cover image is set for the episode, the blog’s cover image will be used.

Podcast Audio

The podcast_audio field is required for an episode. It is used for the enclosure tag in the podcast feed.

Promote > Title

This will be used as the title of the episode in the Podlove Web Player.

Promote > Description

This will be used as the description of the episode in the Podlove Web Player.

Keywords

Keywords are set in the podcast feed as the iTunes keywords tag.

Explicit

Explicit content is set in the podcast feed as the iTunes explicit tag.

Block

Indicates whether the episode is blocked from iTunes or not.

Categories / Tags

This is a beta feature. It is not yet fully implemented. Since I don’t know yet if I will go with tags or categories, I added both and wait which one sticks 😄.

Categories

Categories are one way to group posts. They come with their own snippet model so you can add them via the admin interface by clicking on one of the categories. A blog post can have multiple categories and a category can have multiple blog posts. If you want to add a new category, you have to add it using the wagtail admin interface.

Categories might be the right thing if you do not have too many of them and they rarely change.

Tags

Tags are another way to group posts. They come with their own link to the taggit tag model. You can add tags to a blog post by using the standard wagtail tag interface. A blog post can have multiple tags and a tag can have multiple blog posts. If you want to add a new tag, there’s a text field with auto completion in the wagtail admin interface.

Tags might be the right thing if you have a lot of them and they change often and you don’t mind having to type them in the admin interface.

Image

Images are mostly just the normal Wagtail Images. But they are rendered using a picture tag supporting srcset and sizes attributes for responsive images.

Video

You can upload video files to the server and play them back in the browser.

Videos have a title a file field original pointing to the original video file, an image poster and a list of tags.

If no poster is given, the first frame of the video after one second is used as poster.

Audio

You can upload audio files to the server and play them back in the browser.

Audio Models

Audio files are represented by the Audio model. Audio files have a title, a subtitle, tags and four file fields pointing to the file in different audio formats:

  • m4a - AAC, works best on Apple/iOS devices

  • mp3 - MP3, works everywhere, but has no time index so the whole file has to be downloaded before playback can start

  • oga - OGG Vorbis, maybe remove this one because apple is now adding support for opus, too

  • opus - Opus, better quality per bitrate than all other formats, but not as well known as the others

Since podcast feeds only support one audio file per episode, there is one feed per audio format. The feeds are generated automatically and can be found at feed/podcast/<audio_format>/rss.xml.

Playback

For playback of audio content Podlove Web Player version 5 is used.

Hint

Currently supported features:

  • Chapter marks

  • Download button

Templates / Themes

You can choose different templates for the entire website or for specific blogs / podcasts. All users can select these templates from the Wagtail admin interface. Individual users can make their own template selections, which are stored in their session.

  • Plain HTML (plain) - This is just a plain HTML template without any CSS

  • Bootstrap 4 (bootstrap4) - This is a template that uses Bootstrap 4 and is currently the default template

  • Bootstrap 5 (bootstrap5) - This is a template that uses Bootstrap 5 and is the theme that I usually use for my own projects

  • Vue.js - This is a template that uses Vue.js and demonstrates how to combine an SPA frontend with django-cast

If you want to use your own templates, you can do so by overwriting the built-in templates or creating a new directory in your project’s templates directory and name it cast/{your_template_name}. Then you can create your own templates in this directory. After all of the following template names are added, you should be able to select your custom template in the Wagtail admin interface.

Important

This is the minimal list of templates that have to be implemented for a template named minimal:

  • cast/minimal/base.html

  • cast/minimal/blog_list_of_posts.html

  • cast/minimal/post.html

  • cast/minimal/post_body.html

  • cast/minimal/episode.html

  • cast/minimal/select_template.html

Hint

It’s only possible to create own template themes with template loaders that implement the get_dirs method (FilesystemLoader, CachedLoader). If you want to use a template loader that doesn’t implement the get_dirs method, you have to add it to settings.CAST_CUSTOM_THEMES.

An list of additional templates that can be overwritten:

  • cast/custom/audio.html

Galleries

Galleries can have different layouts. Currently there are two layouts:

  • default - This is the default layout which will use the cast/minimal/gallery.html template

  • htmx - Which will use the cast/minimal/gallery_htmx.html template

If you don’t want to implement the htmx layout, you can just copy your cast/minimal/gallery.html template to cast/minimal/gallery_htmx.html.

How to Change the Theme for the whole Site

This setting can be found at settings > Template base directory:

Set the theme or "template base directory" for the whole site

There’s a context processor that adds the current template base directory aka theme to the context. For convenience it also adds the theme’s base template as a variable to the context to be able to extend it in non Wagtail templates.

Error Views

If you want to use your own error views, you can do so by creating templates for each error code in your theme’s directory.

Hint

This is the list of templates that you can overwrite

  • cast/your_theme/404.html

  • cast/your_theme/500.html

  • cast/your_theme/400.html

  • cast/your_theme/403.html

  • cast/your_theme/403_csrf.html

Since now the error views need to know which theme to use, you have to overwrite the default error views in your project’s root URL-conf:

...
from cast.views import defaults as default_views_cast

handler404 = default_views_cast.page_not_found
handler500 = default_views_cast.server_error
handler400 = default_views_cast.bad_request
handler403 = default_views_cast.permission_denied

Setting the view for the 403_csrf error is a special case. You have to specify the view in your project’s settings:

...
# view handling csrf failures
CSRF_FAILURE_VIEW = "cast.views.defaults.csrf_failure"

How to Change the Theme for a Single Blog

This setting can be found at pages > … > Blog:

Set the theme or "template base directory" for a single blog

How to Change the Theme for an Individual User

The theme selection for an individual user is stored in request.session and does overwrite blog and site level theme settings.

JSON-Api

You can get a list of selectable themes via the cast:api:theme-list endpoint. This endpoint will also show the currently selected theme. If you want to update the selected theme, you can do so via cast:api:theme-update.

Hypermedia

The hypermedia endpoints for getting / setting the theme are:

  • cast:theme-list - List of all themes (the currently selected theme is marked)

  • cast:theme-update - Update the theme for the current user

Django-Admin Commands

There are some management-commands bundled with django-cast. Most of them are dealing with the management of media files.

  • recalc_video_posters: Recalculate the poster images for all videos.

  • media_backup: Backup media files from production to backup storage backend (requires Django >= 4.2).

  • media_sizes: Print the sizes of all media files stored in the production storage backend (requires Django >= 4.2).

  • media_replace: Replace files on production storage backend with versions from local file system (requires Django >= 4.2). This might be useful for videos for which you now have a better compressed version, but you don’t want to generate a new name.

  • media_restore: Restore media files from backup storage backend to production storage backend (requires Django >= 4.2).

  • media_stale: Print the paths of all media files stored in the production storage backend that are not referenced in the database (requires Django >= 4.2).

  • sync_renditions: Create missing renditions for images and remove obsolete ones. Useful if you have changed the rendition sizes in your settings or added new image formats.