What happens to a plugin's table when the plugin goes #128
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
Reference
Postulo/postulo#128
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
Third prerequisite, and the one that only bites the kinds that hold data. A self-contained
plugin that owns a model owns a table, and a table outlives the package that made it.
What exists
Every model in Postulo lives in a Postulo app with a Postulo migration. The live example is
the newest one:
phone-numbersis a plugin, and everything it governs is in core —core.PhoneNumber,core/migrations/0007_phonenumber.py, the data migration that carriedthe old single field across, and a
GenericRelationon two holders in two other apps.Make that plugin self-contained in the sense the imperative asks for and it becomes a
Django app of its own with a migration history of its own. Then three questions arrive that
do not exist today:
table, removing it leaves either a table with no model — invisible to
migrate, presentin every backup, holding somebody's telephone numbers — or a schema change that deletes
the data. Neither is currently possible, because no plugin owns a table.
deletes nothing, in the interface, in 39 languages. The distinction between off and
uninstalled has never had to carry weight before and now would.
INSTALLED_APPSto load themodel. A plugin uninstalled while its rows remain means either an app that must stay
installed after its package is gone, or a start-up that fails on a migration referring to
a module that no longer imports.
What this asks for
A written rule for what happens to a plugin's data when the plugin goes, and a mechanism
that makes it true — before any plugin is moved out of core carrying a table with it.
Worth being careful about
Backups and exports are the safety net and they have to know.
manage.py backupand theaccount export walk models; a table owned by a package that is no longer installed is a
table neither can see. Somebody's numbers would be absent from their export without anything
saying so, which is the failure mode the export exists to prevent.
The honest options are few. Refuse to uninstall while rows exist, and say what holds it;
uninstall and keep the table, with a way to see what orphaned tables remain; or uninstall and
delete, behind a confirmation naming the count. Each is defensible and they are not equally
safe; picking one is the substance of this issue.
Only some kinds are affected. The two sources, the importer and the transport hold
nothing and could be moved out of core with none of this decided. That makes a staged answer
possible: move the stateless ones first, and let this issue block only the ones that own
data.
A plugin that ships inside the image cannot really be uninstalled, which is a mitigation
worth stating: the built-ins arrive with Postulo and go when it does. The risk arrives with
the third-party plugin that copies the pattern, which is precisely who the documentation is
written for.
The rule, which the issue said was the substance:
Postulo refuses, names how many records are in the way, and leaves both the package and the
data alone.
Why not the other two. Keep the table leaves data nothing can read, export or restore —
present in every backup, absent from the export of the person whose data it is, invisible to
migrate. That is exactly the failure the issue named. Delete behind a confirmation makesremoving a package a data-destroying act, when somebody may only be swapping it for a newer
build of the same plugin — and it puts uninstall on the wrong side of a promise Postulo makes
in thirty-nine languages, that switching a plugin off deletes nothing. Nobody holds that
distinction in their head at the moment it matters.
Refusing is also the shape already used three times here: the last administrator, the mail
transport that is the last way in, the plugin that is somebody's only recovery route.
The three questions the issue raised, answered.
remove()rather than only on the page, so amanagement command or a shell meets the same rule.
the plugin off instead — off keeps everything."
the app goes, so its migrations reverse cleanly and there is never a migration referring to
a module that no longer imports.
CONTRIBUTING.mdstates the requirement that a pluginowning a table ships its own migrations and is in
INSTALLED_APPS."Backups and exports are the safety net and they have to know." The export now carries a
pluginssection. A plugin that owns a person's data answersexport_for; one that owns dataand cannot answer has its models named under
not_carried, because a silent gap isdiscovered when somebody restores and a stated one while they still have the original. A plugin
that raises while exporting is named the same way rather than taking a whole archive down.
FORMAT_VERSIONis 8."Only some kinds are affected", and the staged answer is now available.
owns_modelsisabsent from every plugin Postulo ships, so the sources, the importer and the transport can be
moved out of core with none of this in the way — which is what #129 and #147 will want.
The test plugin claims to own
core.Tag. The mechanism deals in labels and does not care wherea model lives, so borrowing a real one exercises all of it without inventing an app that would
exist only for a test.
19 tests in
tests/test_plugin_data.py, the rule inCONTRIBUTING.mdfor plugin authors andin the wiki for operators, and one string in all 39 European catalogues.
Shipped in
41b5af2on0.3.0, withmainkept level.