Coverage for gws-app/gws/base/search/action.py: 0%

78 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Search action.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.base.model 

7import gws.base.action 

8import gws.base.template 

9import gws.base.feature 

10import gws.lib.shape 

11import gws.lib.uom 

12 

13 

14_DEFAULT_VIEWS = ['title', 'teaser', 'description'] 

15_DEFAULT_TOLERANCE = 10, gws.Uom.px 

16 

17 

18@gws.ext.config.action('search') 

19class Config(gws.base.action.Config): 

20 """Runs searches in the project finders and returns the found features.""" 

21 

22 limit: int = 1000 

23 """Max. number of search results.""" 

24 tolerance: Optional[gws.UomValueStr] 

25 """Default tolerance for geometry searches.""" 

26 categories: Optional[list[str]] 

27 """Result categories users can filter search results by.""" 

28 

29 

30@gws.ext.props.action('search') 

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

32 categories: list[str] 

33 

34 

35class Request(gws.Request): 

36 """Request of the ``searchFind`` command.""" 

37 

38 crs: Optional[gws.CrsName] 

39 """CRS of the extent, defaults to the map CRS.""" 

40 extent: Optional[gws.Extent] 

41 """Search extent, defaults to the map extent.""" 

42 keyword: str = '' 

43 """Keyword to search for.""" 

44 layerUids: list[str] 

45 """Layers whose finders are used in addition to the project and application finders.""" 

46 limit: Optional[int] 

47 """Max. number of results, cannot exceed the configured limit.""" 

48 resolution: float 

49 """Pixel resolution for geometry searches.""" 

50 shapes: Optional[list[gws.lib.shape.Props]] 

51 """Shapes to search in; several shapes are combined into one.""" 

52 tolerance: Optional[str] 

53 """Tolerance for geometry searches, a value with a unit, in pixels if no unit is given.""" 

54 views: Optional[list[str]] 

55 """Feature views to render, defaults to ``title``, ``teaser`` and ``description``.""" 

56 categories: Optional[list[str]] 

57 """Result categories.""" 

58 withCategories: bool 

59 """Return result categories.""" 

60 

61 

62class Response(gws.Response): 

63 """Response of the ``searchFind`` command.""" 

64 

65 features: list[gws.FeatureProps] 

66 """Found features with rendered views.""" 

67 

68 

69@gws.ext.object.action('search') 

70class Object(gws.base.action.Object): 

71 """Search action, runs searches for the client.""" 

72 

73 limit = 0 

74 """Max. number of results, 0 for no limit.""" 

75 tolerance: gws.UomValue 

76 """Default tolerance for geometry searches.""" 

77 categories: list[str] = [] 

78 """Result categories users can filter by.""" 

79 

80 def configure(self): 

81 self.limit = self.cfg('limit') or 0 

82 self.tolerance = self.cfg('tolerance') or _DEFAULT_TOLERANCE 

83 self.categories = self.cfg('categories') or [] 

84 

85 def props(self, user): 

86 return gws.u.merge(super().props(user), categories=self.categories) 

87 

88 @gws.ext.command.api('searchFind') 

89 def find(self, req: gws.WebRequester, p: Request) -> Response: 

90 """Run a search and return the found features.""" 

91 

92 return Response(features=self._get_features(req, p)) 

93 

94 def _get_features(self, req: gws.WebRequester, p: Request) -> list[gws.FeatureProps]: 

95 project = req.user.require_project(p.projectUid) 

96 search = gws.SearchQuery(project=project) 

97 

98 if p.layerUids: 

99 search.layers = gws.u.compact(req.user.acquire(uid, gws.ext.object.layer) for uid in p.layerUids) 

100 

101 search.bounds = project.map.bounds 

102 if p.extent: 

103 search.bounds = gws.Bounds(crs=p.crs or project.map.bounds.crs, extent=p.extent) 

104 

105 search.limit = self.limit 

106 if p.limit and int(p.limit) > 0: 

107 search.limit = min(int(p.limit), self.limit) if self.limit else int(p.limit) 

108 

109 if p.shapes: 

110 shapes = [gws.lib.shape.from_props(s) for s in p.shapes] 

111 search.shape = shapes[0] if len(shapes) == 1 else shapes[0].union(shapes[1:]) 

112 

113 search.tolerance = self.tolerance 

114 if p.tolerance: 

115 search.tolerance = gws.lib.uom.parse(p.tolerance, gws.Uom.px) 

116 

117 if p.resolution: 

118 search.resolution = p.resolution 

119 

120 if p.keyword.strip(): 

121 search.keyword = p.keyword.strip() 

122 

123 results = self.root.app.searchMgr.run_search(search, req.user) 

124 if not results: 

125 return [] 

126 

127 mc = gws.ModelContext(op=gws.ModelOperation.read, target=gws.ModelReadTarget.searchResults, project=project, user=req.user) 

128 

129 for res in results: 

130 templates = gws.u.compact( 

131 self.root.app.templateMgr.find_template(f'feature.{v}', where=[res.finder, res.layer, project], user=req.user) 

132 for v in p.views or _DEFAULT_VIEWS 

133 ) 

134 res.feature.render_views(templates, user=req.user, project=project, layer=res.layer) 

135 

136 return [res.feature.model.feature_to_view_props(res.feature, mc) for res in results]