Invariants¶
These rules prevent regressions found in testing or observed against GeoServer. The architecture page explains the pieces they name; the conventions page refers to them by number.
Row cache and tab callbacks are reset together.
_setup_tableclears_all_rowsand_filtered_rows;_reset_table_stateclears those and every callback and the pagination buttons. A loader that fails mid-fetch must leave an empty table, never the previous type’s rows under the new type’s Delete handler. That was a real wrong-target delete (tests/qgis/test_dlg_main.pyguards it).Qt’s table sorting stays off (
_setup_tableforces it). A header click sorts_filtered_rowsitself (_on_header_clicked), so the order on screen is the order of the row cache. Rows are mapped back by index. If Qt reordered the items on its own, Delete Selected would act on a different resource than the one highlighted. The sort survives a reload of the same tab. It is dropped when the columns change, or when it is on a detail column whose cells are pending again after the reload.Edits merge onto what the server has. GeoServer applies a datastore PUT by replacing the whole
connectionParametersmap. Never route an edit through the typedcreate_*helpers. Use_update_datastore_from_values, which overlays only the form’s own keys onto the fetched params and keeps the server’stype. The edit form’s Save fetches them again (_save_datastore_edit) and applies only what the user changed,enabledincluded. So an edit that another client saved while the form was open survives. A store deleted or renamed meanwhile is refused, wherecreate_datastorePOSTed it back empty. The workspace and tile cache forms do the same. GeoServer ignoresenabled: falseon a POST (the store is created enabled, measured on 2.28.5). Only a PUT disables one, which is why the checkbox exists in edit mode only.Add refuses an existing name.
create_workspaceandcreate_datastoreare upserts (POST, then PUT on 409). Check_resource_existsfirst, or a live resource is silently reconfigured and reported “created”.Never show a password. GeoServer returns
passwdandWFSDataStoreFactory:PASSWORDencrypted (crypt1:…) or not at all. Prefilled, the ciphertext would read as the password and get edited into garbage. GeoServer does accept its own ciphertext back. Measured on 2.28.5: a PostGIS store still listed its tables after the round trip, and stopped doing so with a wrong plaintext. So on edit the field is blank. Blank means keep (the stored value is sent back) and typed means replace. The encryption is randomised: the same plaintext saves as a differentcrypt1:value every time, so ciphertexts cannot be compared.A datastore rename is one PUT on the old path. A save through
create_datastoreunder the new name would upsert. It would duplicate the store, or overwrite whatever holds the new name._rename_datastorerefuses a taken name before any request; then the save goes to the new name.Delete confirmations name the cascade. Both delete paths send
recurse=true.isVisible()lies on inactive tab pages.ResourceFormDialogtracks hidden fields in_hidden_keys. Validation uses that, not Qt, and switches to the tab that holds the offending field.A fetch never touches a widget. It runs in a worker thread. Everything it learns comes back as
(rows, failures), and_render_rowsrenders it on the GUI thread. A cancelled or failed load renders nothing. That is safe only because the loader reset the table before it started the task, which is what keeps “no stale rows” true here too. An upload’swork(task)is held to the same rule. The file, the paths and the REST client are arguments captured on the GUI side, and progress goes throughtask.setProgress.A loaded table outlives its connection.
refresh_ui()clearsself.gsat once and re-probes in a task. So for up toPROBE_TIMEOUT, the rows on screen and their buttons belong to a client that is gone. Every user-triggered action therefore passes_require_connection(). That check lives at every place where an action is dispatched. The Add button and Delete Selected are such places. So are the row-action buttons and their More or Actions menu entries. So are the link-cell click, Enter on a row, and the Del key. A selection change re-enables the button while the probe runs. It never lives in the 20 methods behind them, so a new tab cannot forget it. A refresh also disables the header buttons at once; the loader re-arms them. This was a reported crash:AttributeError: 'NoneType' object has no attribute 'get_workspaces'from Publish a Layer. The tab fetchers readself.gsfrom their worker. That is safe for one reason only. The client changes solely after the running load is cancelled (refresh_ui()cancels, then clears), and a cancelled load renders nothing. Anything new that assignsself.gsmust cancel the load first;test_a_refresh_cancels_the_load_before_it_drops_the_clientguards it. A refresh does not cancel a delete batch or an upload, and their later steps readself.gs. A connection switch mid-batch sent the rest of a recursive delete to the other server. Sorefresh_ui()and the connection switch refuse, with a warning, while either runs. They also refuse while a save abandoned at the waiting box still runs in its thread (_refuse_while_writing, which looks for a_ReadThreadwithwriteset). The same dispatch points also pass_addressable(rows). A row whose name holds/ ? # %is refused, becauserequestssendsdatastores/a#basdatastores/a, and the delete of “a#b” deleted “a”.Nav labels in
TABSare logic keys as well as text. Thetr("Actions")column and thetr("Workspace")key of_extra_click_callbacksmust equal the header strings.