Coverage for gws-app/gws/plugin/qfieldcloud/action.py: 98%

40 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-23 20:06 +0200

1"""QField Cloud API action.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.base.auth 

7import gws.base.action 

8import gws.lib.mime 

9import gws.lib.jsonx 

10 

11from . import core, action_base, action_handler 

12 

13 

14@gws.ext.config.action('qfieldcloud') 

15class Config(gws.ConfigWithAccess): 

16 """Endpoint for QField clients that emulates the QFieldCloud API.""" 

17 

18 projects: list[core.ProjectConfig] 

19 """Projects offered to QField clients.""" 

20 auth: Optional[gws.base.auth.method.Config] 

21 """Token authentication method for QField clients.""" 

22 deltaLifeTime: gws.Duration = '30d' 

23 """How long uploaded changes are kept on the server for status requests. (added in 8.5)""" 

24 

25 

26@gws.ext.props.action('qfieldcloud') 

27class Props(gws.base.action.Props): 

28 pass 

29 

30 

31@gws.ext.object.action('qfieldcloud') 

32class Object(action_base.BaseAction): 

33 """QField Cloud API action. 

34 

35 Emulates the QFieldCloud API for the QField app: authenticates clients by 

36 token, lists the configured QField projects, creates packages in background 

37 jobs and applies deltas and file uploads from the devices. 

38 """ 

39 

40 @gws.ext.command.raw('qfieldcloudApi') 

41 def raw_request(self, req: gws.WebRequester, p: gws.Request) -> gws.ContentResponse: 

42 """Handle a QFieldCloud API request.""" 

43 try: 

44 return self.get_handler().handle(req, p) 

45 except gws.NotFoundError as exc: 

46 return _error_response(404, 'object_not_found', exc) 

47 except gws.AuthenticationError as exc: 

48 return _error_response(401, 'authentication_failed', exc) 

49 except gws.ForbiddenError as exc: 

50 return _error_response(403, 'permission_denied', exc) 

51 except gws.BadRequestError as exc: 

52 return _error_response(400, 'validation_error', exc) 

53 except core.DeltaApplyError as exc: 

54 return _error_response(409, 'delta_apply_failed', exc, str(exc)) 

55 

56 def get_handler(self) -> action_handler.Handler: 

57 """Return a new request handler. Override to use a custom handler. 

58 

59 Returns: 

60 Request handler. 

61 """ 

62 return action_handler.Handler(self) 

63 

64 

65## 

66 

67 

68def _error_response(status: int, code: str, exc: Exception, message: str = '') -> gws.ContentResponse: 

69 """Log an error and return a JSON error response, with an optional message shown in QField.""" 

70 gws.log.warning(f'qfieldcloudApi: {status} {code} cause={exc!r}') 

71 js = {'code': code} 

72 if message: 

73 js['message'] = message 

74 return gws.ContentResponse( 

75 status=status, 

76 content=gws.lib.jsonx.to_string(js), 

77 mimeType=gws.lib.mime.JSON, 

78 )