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

76 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 23:09 +0200

1"""Search API.""" 

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.base.shape 

11import gws.lib.uom 

12 

13gws.ext.new.action('search') 

14 

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

16_DEFAULT_TOLERANCE = 10, gws.Uom.px 

17 

18 

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

20 """Search action""" 

21 

22 limit: int = 1000 

23 """Search results limit.""" 

24 tolerance: Optional[gws.UomValueStr] 

25 """Default tolerance.""" 

26 categories: Optional[list[str]] 

27 """Search categories. (added in 8.2)""" 

28 

29 

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

31 categories: list[str] 

32 

33 

34class Request(gws.Request): 

35 crs: Optional[gws.CrsName] 

36 extent: Optional[gws.Extent] 

37 keyword: str = '' 

38 layerUids: list[str] 

39 limit: Optional[int] 

40 resolution: float 

41 shapes: Optional[list[gws.base.shape.Props]] 

42 tolerance: Optional[str] 

43 views: Optional[list[str]] 

44 categories: Optional[list[str]] 

45 withCategories: bool 

46 

47 

48class Response(gws.Response): 

49 features: list[gws.FeatureProps] 

50 

51 

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

53 limit = 0 

54 tolerance: gws.UomValue 

55 categories: list[str] = [] 

56 

57 def configure(self): 

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

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

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

61 

62 def props(self, user): 

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

64 

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

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

67 """Perform a search""" 

68 

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

70 

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

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

73 search = gws.SearchQuery(project=project) 

74 

75 if p.layerUids: 

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

77 

78 search.bounds = project.map.bounds 

79 if p.extent: 

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

81 

82 search.limit = self.limit 

83 if p.limit: 

84 search.limit = min(int(p.limit), self.limit) 

85 

86 if p.shapes: 

87 shapes = [gws.base.shape.from_props(s) for s in p.shapes] 

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

89 

90 search.tolerance = self.tolerance 

91 if p.tolerance: 

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

93 

94 if p.resolution: 

95 search.resolution = p.resolution 

96 

97 if p.keyword.strip(): 

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

99 

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

101 if not results: 

102 return [] 

103 

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

105 

106 for res in results: 

107 templates = gws.u.compact( 

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

109 for v in p.views or _DEFAULT_VIEWS 

110 ) 

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

112 

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