Skip to main content

Spodcat

This is the backend part of my podcast platform. It's designed to go along with the frontend part. It's built on Django REST Framework with a JSON:API implementation, but more on that later. The admin interface is just the regular Django admin with some minor tweakage. My own specific implementation is available in this repo and is also live here.

It's mainly made for my own specific purposes. Lately I have been making some effort to generalise stuff, in order to facilitate some potential wider use. But there's probably lots more that needs to be done to that end.

Spodcat configuration

Spodcat is configured using a SPODCAT dict in your Django settings module. These are the available settings:

FRONTEND_ROOT_URL

Mainly used for RSS feed generation and some places in the admin. Default: http://localhost:4200/.

BACKEND_HOST

Used (along with BACKEND_ROOT, see below) for generating RSS feed URLs which are sent to the frontend, as well as some stuff in the admin. Default: http://localhost:8000/.

BACKEND_ROOT

Set this is your backend installation is not at the URL root. Default: empty string.

USE_INTERNAL_AUDIO_REDIRECT

If True, the episode API responses and RSS feeds will use the internal view spodcat:episode-audio (resolving to something like https://example.com/episodes/<episode-id>/audio/) for episode URLs instead of linking directly to whatever episode.audio_file.url returns. This view will then save a PodcastEpisodeAudioRequestLog entry (provided the spodcat.logs app is installed) and return a 302 (temporary) redirect to episode.audio_file.url.

Possible use cases for this:

  • Your storage provider cannot reliably provide permanent, canonical episode URLs for some reason
  • You want to save PodcastEpisodeAudioRequestLog logs but your storage provider doesn't let you access request logs

Note that the spodcat:episode-audio view has no way to log partial episode downloads, and will log every request as if it's for the entire audio file.

USE_INTERNAL_AUDIO_PROXY

Like USE_INTERNAL_AUDIO_REDIRECT but more involved and with many potential downsides. Basically, when set to True it will make the spodcat:episode-audio view act as a full on proxy instead of just redirecting, i.e. it will fetch the audio file contents from your storage provider and serve them directly. You probably only want to use this if you store episode audio locally on the backend server, or if you really want to be able to log partial episode downloads but your storage provider doesn't let you access request logs. With remote storage backends, it will probably add a bunch of overhead and generally make things a little worse for everyone.

Takes priority over USE_INTERNAL_AUDIO_REDIRECT if True.

FILEFIELDS

Contains settings for various FileFields on different models, and govern where uploaded files will be stored and by which storage engine.

SPODCAT = {
    "FILEFIELDS": {
        "__FILEFIELD_CONSTANT__": {
            "UPLOAD_TO": Callable[[Model, str], str] | str,
            "STORAGE": Storage | Callable[[], Storage] | str,
        },
    },
}

I.e. the UPLOAD_TO values represent FileField.upload_to callables or paths to them, and STORAGE represent the storage parameter of the same FileField (with the addition that they can also be strings, in which case the storage with this key in django.core.files.storage.storages will be used).

Here are the available values for __FILEFIELD_CONSTANT__ and the model types and default values for their UPLOAD_TO settings:

  • EPISODE_AUDIO_FILE: Model is Episode. Default: f"{instance.podcast.slug}/episodes/{filename}"
  • EPISODE_CHAPTER_IMAGE: Model is AbstractEpisodeChapter. Default: f"{instance.episode.podcast.slug}/images/episodes/{instance.episode.slug}/chapters/{filename}"
  • EPISODE_IMAGE: Model is Episode. Default: f"{instance.podcast.slug}/images/episodes/{instance.slug}/{filename}"
  • EPISODE_IMAGE_THUMBNAIL: Same as above
  • FONTFACE_FILE: Model is FontFace. Default: f"fonts/{filename}"
  • PODCAST_BANNER: Model is Podcast. Default: f"{instance.slug}/images/{filename}"
  • PODCAST_COVER: Same as above
  • PODCAST_COVER_THUMBNAIL: Same as above
  • PODCAST_FAVICON: Same as above
  • PODCAST_LINK_ICON: Model is PodcastLink. Default: f"{instance.podcast.slug}/images/links/{filename}"
  • SEASON_IMAGE: Model is Season. Default: f"{instance.podcast.slug}/images/season/{instance.number}/{filename}"
  • SEASON_IMAGE_THUMBNAIL: Same as above

Footnote: The reason for adding the STORAGE settings was that I did my file hosting with Azure, but that didn't work with CSS fonts since I couldn't control the Access-Control-Allow-Origin header. So I did this:

MEDIA_ROOT = os.environ.get("MEDIA_ROOT", BASE_DIR / "media")
MEDIA_URL = "/media/"

STORAGES = {
    "default": {"BACKEND": "storages.backends.azure_storage.AzureStorage"},
    "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
    "local": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
}

SPODCAT = {
    "FILEFIELDS": {
        "FONTFACE_FILE": {"STORAGE": "local"},
    },
    ...
}

... and then just had my web server reply to MEDIA_URL request by serving the files in MEDIA_ROOT.

Other Django settings

This is a bare minimum of apps you need to include in your project:

INSTALLED_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "spodcat",
]

However, this will only be able to run the API. It will not allow you to use the admin or the Django REST Framework browsable API. This is probably more like what you want:

INSTALLED_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",      # needed for admin
    "django.contrib.messages",      # needed for admin
    "django.contrib.staticfiles",   # needed for admin and REST browsable API
    "rest_framework",               # needed for REST browsable API
    "rest_framework_json_api",      # needed for REST browsable API
    "django_filters",               # needed for REST browsable API
    "martor",                       # needed for admin
    "spodcat",
    "spodcat.logs",
    "spodcat.contrib.admin",
]

Here, spodcat.contrib.admin is used instead of django.contrib.admin. It adds some nice stuff like a couple of charts and a notice on the admin index page about comments awaiting approval.

If you somehow don't want to log any page, episode audio, and RSS requests, you can leave out spodcat.logs.

URLs

This root URL conf is perfectly adequate:

from django.urls import include, path

urlpatterns = [
    path("", include("spodcat.urls")),
    path("admin/", include("spodcat.contrib.admin.urls")),
]

(You don't need to include django.contrib.admin.site.urls if you use spodcat.contrib.admin.urls.)

Used software

Metadata

Release files for spodcat-backend 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spodcat-backend 0.11.0
File Size Uploaded
spodcat_backend-0.11.0.tar.gz 35.4 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for spodcat-backend 0.11.0
File Interpreter ABI Platform
spodcat_backend-0.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.2 MB

Release files / spodcat_backend-0.11.0.tar.gz

Download URL spodcat_backend-0.11.0.tar.gz
Size 35.4 MB
Tags Source
SHA-256 checksum
How to use checksums
4ec9eca2681c027ed9a95b809a0318e9e063c512425440665cbbc0573a9ef1cb
BLAKE2b-256 checksum
How to use checksums
d78a5205f9a5cd05e663b5f4c32670e226d509741a5541032ff961cca441631a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.5

Release files / spodcat_backend-0.11.0-py3-none-any.whl

Download URL spodcat_backend-0.11.0-py3-none-any.whl
Size 35.7 MB
Tags Python 3
SHA-256 checksum
How to use checksums
3e6cde2aebac1b0e6fb8076ba417408b36a74ead719970541fb58501a3fdbb8d
BLAKE2b-256 checksum
How to use checksums
5cdcb5f56ef7a3aa21d760b9078036264f28647152e342c03ca61fa5bf8d1007
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.5
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page