Coverage for gws-app/gws/plugin/qfieldcloud/__init__.py: 100%
2 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""QField Cloud plugin.
3This plugin emulates the QFieldCloud API, so that the QField mobile app can
4download projects from GWS and synchronize edits back. The QGIS projects are
5packaged according to the settings made with the QFieldSync QGIS plugin.
7.. rubric:: Submodules
9- ``action``: the ``qfieldcloud`` action. It dispatches API requests to route handlers, authenticates clients by token, runs packaging jobs, stores incoming deltas and manages the package and cache directories.
10- ``api``: data classes and enums mirroring the QFieldCloud API objects (``swagger.yaml``), plus a few objects used by the client that are not in the specification.
11- ``auth``: the ``qfieldcloud`` authorization method, created and registered by the action. It has the fixed uid ``gws.plugin.qfieldcloud.auth``.
12- ``caps``: reads the QFieldSync project and layer properties from the QGIS project and decides, per layer, whether it is packaged for editing, packaged as a base map or removed.
13- ``cli``: the ``qfieldcloudPackage`` command, which creates a package into a directory.
14- ``core``: the configuration and object of a single QField project.
15- ``packager``: writes a package (GeoPackage data, base maps, media files, modified QGIS project).
16- ``patcher``: applies changes ("deltas") and file uploads from QField to the database.
18.. rubric:: Design
20The action holds a list of QField projects (``core.QfcProject``), each built on
21a QGIS project. For each QField project, ``caps.Parser`` creates a ``caps.Caps``
22object with the layer and model map; the action caches it until the QGIS project
23source changes. Packaging and patching both work on these capabilities.
25The action answers requests under the raw command ``qfieldcloudApi``. A rewrite
26rule maps a regular address to that command and names the GWS project::
28 web.sites+ {
29 rewriteRules+ {
30 pattern "^/qfc/(.*)"
31 target "/_/qfieldcloudApi/projectUid/my_project/$1"
32 }
33 }
35QField users enter the resulting address (``https://example.com/qfc``) as the
36server and log in with their GWS credentials. The action brings its own
37authorization method; an auth provider and a session manager must be configured.
38Requests other than the public status and login routes require an
39``Authorization: Token ...`` header with the session token.
41.. rubric:: Configuration
43The plugin exposes an action of type ``qfieldcloud``. In the action config, you can define multiple ``projects``, each representing a QField project::
45 actions+ {
46 type "qfieldcloud"
48 projects+ {
49 title "My Project"
50 provider.path "/path/to/file.qgs"
51 }
52 }
54.. rubric:: Downloading data
56Data flow GWS -> QField, also called "packaging".
58When QField requests a package job, the action runs the packager in a background job.
59In the given QGIS project, the plugin looks for Postgres layers marked as "offline editable", fetches their data and writes each table into a GeoPackage file. A modified QGIS project file is also created, pointing to the GeoPackage layers instead of the original Postgres layers. Layers marked "remove" are removed from the project, and empty layer groups are dropped.
61For each offline table, a Model can be configured to customize field selection and data filtering. Models are defined in the project configuration and matched by table name. By default, the plugin uses a generic Postgres model that includes all fields and all features.
63Base map layers (a map theme or a single layer, as selected in QFieldSync) are rendered through QGIS into a single raster image covering the area of interest at the configured maximum zoom level. The rendered images are cached in the project cache directory for ``mapCacheLifeTime``.
65Directories listed in the QFieldSync attachment, data and copy settings are added to the package as media files.
67.. rubric:: Uploading data
69Data flow QField -> GWS, also called "patching".
71For each incoming "delta" payload from QField, the plugin extracts the created, updated and deleted features and passes them to the respective Model. The Model is responsible for applying the changes to the Postgres database. The payload is stored for an hour, so that QField can poll its status.
73.. rubric:: File uploads
75If a Model supports file uploads, it should contain a virtual file field with ``nameColumn`` and ``contentColumn``::
77 actions+ {
78 type "qfieldcloud"
80 projects+ {
81 title "My Project"
82 provider.path "/path/to/file.qgs"
84 models+ {
85 type "postgres"
86 ...
87 fields+ {
88 type "file"
89 name "virtual_file_field"
90 contentColumn "file_content"
91 nameColumn "file_name"
92 }
93 }
94 }
95 }
97QField sends uploads in two steps: first, the file path is included along with the feature changes in the delta payload. Later, the actual file content is uploaded in a separate request. The plugin matches the file content to the respective feature based on the ``nameColumn`` value and writes it into ``contentColumn``.
99.. rubric:: Extending
101Override the packager and patcher classes to customize packaging and patching behavior.
102In your custom action class, override ``get_packager()`` and ``get_patcher()`` methods to return your custom classes.
104Example::
106 import gws.plugin.qfieldcloud.action
107 import gws.plugin.qfieldcloud.patcher
109 class MyPatcher(gws.plugin.qfieldcloud.patcher.Object):
110 def commit_operations_for_model(self, me, ops):
111 ...
112 super().commit_operations_for_model(me, ops)
114 class MyAction(gws.plugin.qfieldcloud.action.Object):
115 def get_patcher(self):
116 return MyPatcher()
117"""
119from . import (
120 action,
121 packager,
122 patcher,
123 caps,
124)
126__all__ = [
127 'action',
128 'packager',
129 'patcher',
130 'caps',
131]