Skip to main content

No project description provided

Project description

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.

Quick and dirty setup

Just a very crude walkthrough. There are more details on some of the stuff later in this document. I will also skip everything that is just boilerplate Django setup (like how you always want to set up a custom User model right from the start), because that is ludicrously out of scope for this document.

This assumes some kind of Linux machine with an web server and an SQL server of your choice.

~$ mkdir my-podcast-backend
~$ cd my-podcast-backend
~/my-podcast-backend$ python3 -m venv .venv
~/my-podcast-backend$ source .venv/bin/activate
(.venv) ~/my-podcast-backend$ pip install spodcat-backend
(.venv) ~/my-podcast-backend$ django-admin startproject my_podcast .

Change this stuff in ~/my-podcast-backend/my_podcast/settings.py:

INSTALLED_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework",
    "rest_framework_json_api",
    "django_filters",
    "martor",
    "spodcat",
    "spodcat.logs",
    "spodcat.contrib.admin",
]
SPODCAT = {
    "FRONTEND_ROOT_URL": "https://my-podcast.org",
    "BACKEND_HOST": "https://backend.my-podcast.org",
}

Do this to ~/my-podcast-backend/my_podcast/urls.py:

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

Create a ~/my-podcast-backend/uwsgi.ini file that looks something like this:

[uwsgi]
chdir = /home/nutte/my-podcast-backend
module = my_podcast.wsgi:application
master = true
pidfile = /tmp/my-podcast-backend.pid
socket = /tmp/my-podcast-backend.sock
processes = 5
vacuum = true
home = /home/nutte/my-podcast-backend/.venv
plugins = python3
uid = 1000
gid = 1000
chmod-socket = 666
harakiri = 240
http-timeout = 240
socket-timeout = 240
enable-threads = true

Now make something run this UWSGI app (I use the UWSGI Emperor), and get your web server to pass requests to this something.

Some tips

Install a Redis server and django-cachalot to speed up responses. This way, most API and RSS requests won't require a single DB hit until the database is updated. I run my instance (both backend, frontend, and PostgreSQL database) on a Raspberry Pi 5, and RSS feed requests (which are by far the most common ones) normally take between 160 and 300 milliseconds. (Of course, if you are planning on hosting popular podcasts, you may want a bit more power.)

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}"

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spodcat_backend-0.6.6.tar.gz (35.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spodcat_backend-0.6.6-py3-none-any.whl (35.7 MB view details)

Uploaded Python 3

File details

Details for the file spodcat_backend-0.6.6.tar.gz.

File metadata

  • Download URL: spodcat_backend-0.6.6.tar.gz
  • Upload date:
  • Size: 35.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.2

File hashes

Hashes for spodcat_backend-0.6.6.tar.gz
Algorithm Hash digest
SHA256 c230bf6fb1cbb56d7d9cc473fa5884c9f29f3033f60a18f30e95acbf1dd17c7c
MD5 4f39ad0a029277f171539e5c55b9157e
BLAKE2b-256 7572878c4225a04f1c2a88f36300f4a9594c9cfb3810d7660ba54588f19088f1

See more details on using hashes here.

File details

Details for the file spodcat_backend-0.6.6-py3-none-any.whl.

File metadata

File hashes

Hashes for spodcat_backend-0.6.6-py3-none-any.whl
Algorithm Hash digest
SHA256 76388e1b7143157e6431974167f271dc730bbdd04e2092d7aa3c3e3499d85f44
MD5 8c076bd18751cece2b2f5475b0b3201b
BLAKE2b-256 b9ad70f1812fd6855065e0f9746ee4eeeac17951285153e1d8d2cfb53b3d80ed

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page