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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Search action."""
3from typing import Optional
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
14_DEFAULT_VIEWS = ['title', 'teaser', 'description']
15_DEFAULT_TOLERANCE = 10, gws.Uom.px
18@gws.ext.config.action('search')
19class Config(gws.base.action.Config):
20 """Runs searches in the project finders and returns the found features."""
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."""
30@gws.ext.props.action('search')
31class Props(gws.base.action.Props):
32 categories: list[str]
35class Request(gws.Request):
36 """Request of the ``searchFind`` command."""
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."""
62class Response(gws.Response):
63 """Response of the ``searchFind`` command."""
65 features: list[gws.FeatureProps]
66 """Found features with rendered views."""
69@gws.ext.object.action('search')
70class Object(gws.base.action.Object):
71 """Search action, runs searches for the client."""
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."""
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 []
85 def props(self, user):
86 return gws.u.merge(super().props(user), categories=self.categories)
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."""
92 return Response(features=self._get_features(req, p))
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)
98 if p.layerUids:
99 search.layers = gws.u.compact(req.user.acquire(uid, gws.ext.object.layer) for uid in p.layerUids)
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)
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)
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:])
113 search.tolerance = self.tolerance
114 if p.tolerance:
115 search.tolerance = gws.lib.uom.parse(p.tolerance, gws.Uom.px)
117 if p.resolution:
118 search.resolution = p.resolution
120 if p.keyword.strip():
121 search.keyword = p.keyword.strip()
123 results = self.root.app.searchMgr.run_search(search, req.user)
124 if not results:
125 return []
127 mc = gws.ModelContext(op=gws.ModelOperation.read, target=gws.ModelReadTarget.searchResults, project=project, user=req.user)
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)
136 return [res.feature.model.feature_to_view_props(res.feature, mc) for res in results]