Community apps
Flashlights is one generic core, released as a container image. Most communities deploy that image as it is: their data is in their database, their look is in environment settings, and they need no repository. A community that needs code the core should not carry, a page of its own, a report, a model, adds one small Django app that plugs into the core image. The core never carries a community's code.
This page is for the developer writing such an app. It says how the app is loaded, what it can do, and which parts of the core it may rely on.
How an app is loaded
EXTRA_APPS is a comma-separated list of Django apps, each the dotted path
of a package the image can import. Empty or unset, the core runs bare. For
each app named, the core:
- puts it in
INSTALLED_APPSahead of its own apps, so the app's models, admin, management commands, signals andAppConfig.ready()work as in any Django project; - searches the app's
templates/folder before the core's own, so a file at the same path replaces a core template; - serves the app's
static/files through the same static files pipeline, collected at image build like the core's. A library the app's pages need is vendored there too, never loaded from a CDN (Third-party code and services); - includes the app's
urlsmodule, when it has one, under a prefix.
The URL prefix is the app's prefix attribute in its urls module
(prefix = "summit/"), or, when the module sets none, the last part of the
app's module path with hyphens for underscores (flashlights_summit is
served under /flashlights-summit/). The module's app_name becomes the
namespace, so the app's templates reverse {% url 'summit:schedule' %} as
usual. A prefix must not be one the core uses (speakers/, events/,
accounts/, api/, admin/ and the rest in portal/urls.py).
A misspelled entry fails the boot by name:
EXTRA_APPS names 'flashlights_sumit', which is not an importable package.
The community's repository
A plain repository, not a fork of the core, holding the app, its tests, a
Dockerfile and the deployment. The Dockerfile is a few lines:
FROM ghcr.io/mariatta/flashlights:0.1.0
COPY . /community
RUN pip install /community
ENV EXTRA_APPS=flashlights_summit
RUN BOOT_CHECK=0 SECRET_KEY=build-only DJANGO_ALLOWED_HOSTS=localhost \
DATABASE_URL=postgres://localhost:5432/db \
python manage.py collectstatic --noinput --clear
The second collectstatic adds the app's static files to the image, with
the placeholder values the core's own build uses. Everything else about
running the image is the core's boot contract: the same
processes, the same variables, the same hosts. The community's name,
colours, logo and email settings stay environment variables, as for any
deployment. Upgrading the core is a one-line change to the FROM tag in
a pull request, and the repository's CI should build the image, run the
app's tests against the new core and load the community's demo data into
a fresh database, so a release that breaks the app fails there first.
Releasing lists the images each release publishes; read
the version's CHANGELOG.md entry before upgrading. Dependabot's docker
updates in the community's repository open that pull request when a new
core version is published.
Replacing a page, an email or a static file
Put a file at the same path inside the app:
| To replace | Put the file at |
|---|---|
The landing page (templates/portal/index.html in the core) |
flashlights_summit/templates/portal/index.html |
| The speaker invitation email body | flashlights_summit/templates/emails/speakers/invitation.md |
The portal stylesheet (portal/static/css/portal.css in the core) |
flashlights_summit/static/css/portal.css |
Start from the core's file at the version the image is built from and change only what you must. A replaced file is a copy: when a core release changes the original, the copy keeps the old version and nothing warns you. Before each upgrade, compare your copies with the new release's files. Prefer the smaller tools where they exist: a setting, a template block you extend, a signal. If the block or setting you need is missing, open an issue describing the need; a change that another community could use belongs in the core.
Where a change goes
New work goes to the core by default. A community repository gets only code no other community could use.
| The change | Where |
|---|---|
| Anything another community could use, even when one community asked for it | The core, generic and configurable |
| A community's data, name, colours, links | Its deployment's database and settings, never a repository |
| A page, report or model only one community uses | The community app |
| A community-only change to what a core view or form does | An extension point in the core first, then the app uses it |
| A core file edited in a community repository | Never |
| An urgent fix in core code | A core patch release, then deployments bump their tag |
| An urgent fix in community code | The community repository, no core release |
The test for any change: would a meetup group or another conference want it? If yes, it is core.
Extension points
An app can add anything on its own: pages, models, reports, commands, emails. Changing what a core view or form does goes through a deliberate extension point, added to the core the first time a community needs one. The shapes the core will use, in the order they are likely to arrive:
- Swappable classes as settings, the way Django swaps
EMAIL_BACKEND: a setting names the form or class to use in place of the core's. - Signals at the moments communities care about, such as a confirmed registration, an accepted proposal or a published edition.
- Named template blocks in the core's templates, so an app extends a page instead of copying it.
- A navigation hook, so an app's pages appear in the menus without replacing the base template.
None of these exists yet. Ask for the one you need with an issue that says what the app would do with it.
Public API
A community app depends on parts of the core, so those parts are a contract. Until 1.0 every upgrade deserves a real test pass, not only a tag bump; from 1.0 the changelog marks any change to these as breaking:
- the environment variables, as the environment reference lists them;
EXTRA_APPS: that the apps go first inINSTALLED_APPS, that theirtemplates/andstatic/win over the core's, and how theirurlsmodule is included (theprefixattribute, the default prefix, theapp_namenamespace);- the template names and paths an app may replace, and, once they exist, the named blocks inside them;
- the swappable settings and the interfaces their classes implement, and the signals and their arguments, once they exist;
manage.py load_preset <directory>and what a preset'sseed.pymay rely on: the models below and the management commands;- the models a community app may point a foreign key at:
Conference,EventDate,VenueandRoom;TeamandVolunteerProfile;Presenter,Session,ProposalandChecklistItem;SponsorshipProfileandSponsorshipTier;PortalProfileandHandle. Point at them, never alter them; a field a community needs on a core model is a core change; - the management commands.
Private, and free to change in any release: everything else, including view classes, form classes, template context names, URL names other than the public pages', and the internal modules.