django-mirroring
Django add-on for production database mirror refresh and staging restore. Uses Dumpling for in-stream anonymisation and Postgres shadow-database cutover so consumers never see a half-loaded mirror.
Links
Supported versions
This package supports Django 6.0 and Python 3.12+. Dependencies are compatible-release pinned; see Pinned dependencies.
Installation
Pick the command for your preferred package installer:
uv add django-mirroring
pip install django-mirroring
# or from GitHub while unpublished:
pip install git+https://github.com/ababic/django-mirroring.git
Add "mirroring" to INSTALLED_APPS and run migrations:
python manage.py migrate mirroring
Pinned dependencies
Every dependency is pinned to a compatible release (~=) so a mirror run cannot
silently pick up an incompatible Dumpling policy format or AWS SDK behaviour.
| Dependency | Pin | Used by |
|---|---|---|
| Python | >=3.12 |
all |
Django |
~=6.0.0 |
all |
dj-database-url |
~=2.2.0 |
temporary database aliases |
python-dateutil |
~=2.9.0 |
retain-window cutoffs |
dumpling-cli |
~=0.9.0 |
refresh_database_mirror anonymisation |
boto3 |
~=1.42.0 |
sync_referenced_media |
dumpling-cli ships the dumpling executable, so installing this package also
pins the CLI. The commands verify it at start-up and refuse to run against a
different minor series.
Postgres client tools (pg_dump, psql) are system packages, not pip
dependencies, so they are pinned as a minimum major — pg_dump refuses to
dump a server newer than itself, while newer clients read older servers fine:
| Setting / env | Default | Purpose |
|---|---|---|
MIRRORING_POSTGRES_CLIENT_MAJOR |
15 |
Minimum pg_dump / psql major; set to the highest server major you mirror from |
Commands fail fast with a clear error when a tool is missing or too old, rather than part-way through a dump.
Dumpling policy (project-owned)
Anonymisation rules live in a project-owned Dumpling TOML file, not inside this
package. Point the refresh command at it with MIRROR_DUMPLING_CONFIG (env) or
MIRROR_DUMPLING_CONFIG (Django setting). The effective config may also embed
generated row_filters and staff username keep rules at refresh time.
Settings overview
Configure via Django settings and/or environment variables (host projects typically wire env → settings in one place).
| Setting / env | Purpose |
|---|---|
MIRROR_SOURCE_DATABASE_URL |
pg_dump source (prefer a full-access follower/replica) |
MIRROR_DATABASE_URL |
Published mirror database (destination for refresh; source for restore) |
MIRROR_DUMPLING_CONFIG |
Path to project Dumpling TOML (required for refresh) |
MIRROR_EXCLUDED_SCHEMA |
Schemas omitted from dump (default: none — set in host settings) |
MIRROR_EXCLUDED_TABLES |
Tables omitted entirely |
MIRROR_EXCLUDED_TABLE_DATA |
Tables whose data is omitted (schema kept) — build with build_mirror_excluded_table_data() |
MIRROR_ROW_RETAIN |
Per-table datetime retain specs for Dumpling row_filters |
MIRROR_RETAIN_MONTHS |
Months of row history to keep (0 disables) |
MIRROR_RESTORE_TARGET_DATABASE_URL |
Staging DB replaced by restore_from_mirror |
MIRROR_RESTORE_ALLOW |
Must be 1 to run restore/revert |
MIRROR_RESTORE_STAFF_EMAIL_DOMAINS |
Comma-separated staff email domains (username keep + restore rematerialisation) |
MIRRORING_AUTO_REGISTER_ADMIN |
Register admin model (default: True) |
MIRRORING_ADMIN_SITE |
Optional dotted path to a custom AdminSite (e.g. "core.admin.site") |
MIRRORING_POSTGRES_CLIENT_MAJOR |
Minimum pg_dump / psql major (default: 15) |
DUMPLING_GLOBAL_SALT must be set in the environment for Dumpling lint/run.
Endpoint guidance (operators)
Refresh and restore only refuse when source and destination resolve to the
same host/port/database (restore/revert also require MIRROR_RESTORE_ALLOW=1
and --confirm). Which databases those env URLs point at is otherwise an
operator responsibility — document your project's URLs carefully; there is no
hostname allow/block list in the package:
- Prefer pointing
MIRROR_SOURCE_DATABASE_URLat a full-access follower or offline replica sopg_dumpcan read every table Dumpling anonymises, and so refresh load does not compete with live writes. Dumping the primary is allowed but not recommended under load. - Prefer pointing
MIRROR_DATABASE_URLat a separate mirror database from the live app primary. The destination role needsCREATEDBfor shadow load + rename cutover. - Point
MIRROR_RESTORE_TARGET_DATABASE_URLonly at a disposable staging (or equivalent) database you intend to replace. - A restricted / allow-listed role that cannot
SELECTPII tables will produce an incomplete or failing dump — use full-access credentials for the source. - Put
sslmodeon connection URLs when the server requires TLS (no hostname-based SSL inference). - Omit provider schemas (e.g. Heroku's
heroku_ext/_heroku) viaMIRROR_EXCLUDED_SCHEMAin the host project when needed.
Management commands
| Command | Role |
|---|---|
refresh_database_mirror |
Nightly production job: dump follower → Dumpling → shadow DB → rename cutover |
restore_from_mirror |
Replace staging from the mirror via shadow load + rename cutover |
revert_mirror_restore |
Swap {target}_preswap back after a restore |
sync_referenced_media |
After restore: copy DB-referenced S3 keys from a source bucket into AWS_STORAGE_BUCKET_NAME |
Selective media sync (separate buckets)
When staging must not share the production media bucket, run
sync_referenced_media after restore_from_mirror. It collects keys from every
FileField / ImageField (honouring private storage location prefixes) plus
optional host collectors, then CopyObjects only those keys.
| Setting / env | Purpose |
|---|---|
MEDIA_SYNC_SOURCE_BUCKET |
Production (or mirror-source) media bucket to read from |
MEDIA_SYNC_SOURCE_REGION |
Optional source region (defaults to AWS_DEFAULT_REGION) |
MEDIA_SYNC_ALLOW |
Must be 1 for a live copy (--dry-run does not need it) |
MEDIA_SYNC_EXTRA_COLLECTORS |
List of dotted callables yielding extra relative keys (JSON path bags, CharFields, …) |
MIRRORING_ANONYMISE_MEDIA_FIELDS |
Models/fields to anonymise: app.model or app.model.field (skip CopyObject; plant placeholders) |
MIRRORING_ANONYMISE_MEDIA_PROVIDER |
Optional dotted callable (MediaObjectRef) -> MediaAnonymiseSpec | None |
AWS_STORAGE_BUCKET_NAME |
Destination bucket (current env) |
python manage.py sync_referenced_media --dry-run
MEDIA_SYNC_ALLOW=1 python manage.py sync_referenced_media --confirm
Default behaviour skips keys already present on the destination (--skip-existing).
Missing source keys are counted and skipped (common when DB rows outlive deleted
objects).
Anonymising PII media
List models or fields that must not be copied as-is. Those keys are not
copied from production; instead a placeholder is PutObject'd at the same
destination key. Image/PDF placeholders are seeded from the source object's
ETag (content fingerprint) so they stay visually distinct without copying real
bytes.
# settings.py
MIRRORING_ANONYMISE_MEDIA_FIELDS = [
"listing.shipment", # dispatch/return labels + courier XML
"ebay.ebaycoupondownload", # coupon transaction CSVs
"data_reporting.exporteddata", # admin exports
# or field-level: "reskinned_inventory.picture.preview",
]
# Optional override; return None to fall back to suffix defaults:
# MIRRORING_ANONYMISE_MEDIA_PROVIDER = "myapp.media_sync.anonymise_for_ref"
Omit a collector from MEDIA_SYNC_EXTRA_COLLECTORS to skip that path bag entirely.
Admin
MirrorDatabaseState is a read-only singleton watermark (generation + restore time).
By default it registers on django.contrib.admin.site. Set MIRRORING_ADMIN_SITE
to your project's admin site (for example "core.admin.site") or call
mirroring.admin.register_admin(site) yourself with MIRRORING_AUTO_REGISTER_ADMIN = False.
Development
just install
just test
just lint
just coverage
See CONTRIBUTING.md for the full recipe list and release process.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_mirroring-0.2.3.tar.gz.
File metadata
- Download URL: django_mirroring-0.2.3.tar.gz
- Upload date:
- Size: 34.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd35cc35f4b51af9d115d2107bf8081ff35bf0b99b4eac119184673b73078b51
|
|
| MD5 |
6db7f0bff0a0fc462e7fc5461db765b8
|
|
| BLAKE2b-256 |
bf4cf9107ecc1fd861dad5e98e2bc94ec98589b84b336a132b255dfb2af63091
|
Provenance
The following attestation bundles were made for django_mirroring-0.2.3.tar.gz:
Publisher:
publish.yml on ababic/django-mirroring
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_mirroring-0.2.3.tar.gz -
Subject digest:
cd35cc35f4b51af9d115d2107bf8081ff35bf0b99b4eac119184673b73078b51 - Sigstore transparency entry: 2499276397
- Sigstore integration time:
-
Permalink:
ababic/django-mirroring@d5a7735340b5e8ac58281a178451cd18c6614d3c -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/ababic
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d5a7735340b5e8ac58281a178451cd18c6614d3c -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_mirroring-0.2.3-py3-none-any.whl.
File metadata
- Download URL: django_mirroring-0.2.3-py3-none-any.whl
- Upload date:
- Size: 45.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b23eb5f13a91671be45d505d77e60c40090f86ff6b0b71a0afcb050056951ad
|
|
| MD5 |
b08efc8d59eed68a3e0d1815ac4b6293
|
|
| BLAKE2b-256 |
d026b97a6b0ececa76c24e44b86f5bbdc7abca4e46b7c2b3c34d9137b106193d
|
Provenance
The following attestation bundles were made for django_mirroring-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on ababic/django-mirroring
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_mirroring-0.2.3-py3-none-any.whl -
Subject digest:
3b23eb5f13a91671be45d505d77e60c40090f86ff6b0b71a0afcb050056951ad - Sigstore transparency entry: 2499276407
- Sigstore integration time:
-
Permalink:
ababic/django-mirroring@d5a7735340b5e8ac58281a178451cd18c6614d3c -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/ababic
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d5a7735340b5e8ac58281a178451cd18c6614d3c -
Trigger Event:
release
-
Statement type: