-
Notifications
You must be signed in to change notification settings - Fork 2
Adds breadcrumbs to search resutls for Topic and News #2282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
|
Not sure if the breadcrumbs color in search shall be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds breadcrumb navigation to search results for various content types (Topics, News, People, Files, Tickets, Events, Directory Entries, etc.) to provide better context when viewing search results. The implementation includes:
Changes:
- Renamed
PageLayouttoTopicLayoutthroughout the codebase for clarity - Added a new
get_layout()method to the request object to retrieve layouts for model instances - Implemented a layout registry system via a new
Layoutdirective - Updated search result templates to display breadcrumbs for various content types
- Added breadcrumb styling and fixed CSS issues related to z-index stacking
- Added file ID anchors and highlight styling for targeted files
- Updated test selectors to use regex anchors for more precise matching
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/onegov/winterthur/test_search.py | Updated click selectors to use regex anchors for precise matching |
| tests/onegov/town6/test_views_directory.py | Updated click selectors to use regex anchors for precise matching |
| tests/onegov/org/test_views_ticket.py | Updated click selectors to use regex anchors for precise matching |
| tests/onegov/org/test_views_directory.py | Updated click selectors to use regex anchors for precise matching |
| tests/onegov/org/test_layout.py | Updated imports from PageLayout to TopicLayout |
| src/onegov/town6/theme/styles/town6.scss | Added z-index reset for breadcrumb links |
| src/onegov/town6/theme/styles/search.scss | Added breadcrumb styling and updated search preview styles |
| src/onegov/town6/theme/styles/files.scss | Added highlight and scroll margin for targeted file rows |
| src/onegov/town6/templates/macros.pt | Added breadcrumbs to search result macros, changed p to div tags for consistency |
| src/onegov/town6/layout.py | Renamed PageLayout to TopicLayout, added layout decorators for multiple models, implemented breadcrumbs for GeneralFile and various RIS models |
| src/onegov/org/views/page.py | Updated imports and references from PageLayout to TopicLayout |
| src/onegov/org/views/editor.py | Updated imports and references from PageLayout to TopicLayout |
| src/onegov/org/request.py | Added get_layout() method to retrieve layouts for model instances |
| src/onegov/org/layout.py | Renamed PageLayout to TopicLayout, added FormDefinitionLayout, DirectoryLayout, improved breadcrumbs for various layouts |
| src/onegov/org/exports/base.py | Fixed import to use onegov.org instead of onegov.town6 |
| src/onegov/org/directives.py | Added Layout directive for registering layouts to models |
| src/onegov/org/app.py | Added layout directive to OrgApp |
| src/onegov/agency/views/page.py | Updated imports and references from PageLayout to TopicLayout |
| src/onegov/agency/layout.py | Updated imports and class name from PageLayout to TopicLayout |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/onegov/town6/layout.py
Outdated
| Link( | ||
| self.model.meeting.title, | ||
| self.request.link(self.model.meeting) | ||
| ), | ||
| Link(title, self.request.link(self.model)) |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The breadcrumbs list includes both self.model.meeting.title (line 1239) and title (line 1242), where title is the same meeting title with an optional date suffix. This creates redundant breadcrumb entries pointing to the same meeting but with different labels. Consider removing the duplicate entry or clarifying the intended breadcrumb structure.
| layout_registry: dict[type, Layout] | ||
| ) -> str: | ||
| return str(self.model) | ||
|
|
||
| def perform( # type:ignore[override] | ||
| self, | ||
| layout: Layout, | ||
| layout_registry: dict[type, Layout] |
Copilot
AI
Jan 13, 2026
•
edited by Daverball
Loading
edited by Daverball
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type annotation for layout_registry should be dict[type, type] instead of dict[type, Layout] since the layout parameter represents a layout class (type), not an instance of the Layout directive class. The perform method stores layout classes in the registry, not Layout directive instances.
| layout_registry: dict[type, Layout] | |
| ) -> str: | |
| return str(self.model) | |
| def perform( # type:ignore[override] | |
| self, | |
| layout: Layout, | |
| layout_registry: dict[type, Layout] | |
| layout_registry: dict[type, type[Layout]] | |
| ) -> str: | |
| return str(self.model) | |
| def perform( # type:ignore[override] | |
| self, | |
| layout: type[Layout], | |
| layout_registry: dict[type, type[Layout]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cleaned up the type annotations to still include Layout, but otherwise a correct suggestion.
| .search-preview em { | ||
| color: $primary-color !important; | ||
| font-style: italic; | ||
| font-weight: bold; | ||
| text-decoration: underline; | ||
| } |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CSS selector has been changed from .search-results em to .search-preview em, narrowing the scope to only emphasize text within search preview sections. This change may affect emphasis styling for other parts of search results that fall outside the search-preview div.
BreathingFlesh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would give the breadcrumbs a light background so they look more like a separate element
Remove wrong translation for title Co-authored-by: Copilot <[email protected]>
…m:OneGov/onegov-cloud into feature/ogc-2880-search-results-with-path
Co-authored-by: Copilot <[email protected]>
…m:OneGov/onegov-cloud into feature/ogc-2880-search-results-with-path
Daverball
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but we should also register layouts for Org. Other than there's some minor things.
We can probably do a better job for GeneralFile, since only publications will have a simple link to a list of files, we should look at which models have linked to the file and then display the breadcrumbs for each of those models. Performance is definitely a concern when looking for links, so we should probably cache them on the model, just like we cache the access of the linked models. But we can improve handling of general files in a follow-up change, for now I would only display breadcrumbs for publications, we can maybe link to the files management view for logged in users, but I'm not sure it's worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to also register the layouts for OrgApp here, while we still have some non-Town6 derived apps. Just like with views, the Town6 layouts will take precedence when registered for the same model in a Town6 app.
| layout_registry: dict[type, Layout] | ||
| ) -> str: | ||
| return str(self.model) | ||
|
|
||
| def perform( # type:ignore[override] | ||
| self, | ||
| layout: Layout, | ||
| layout_registry: dict[type, Layout] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cleaned up the type annotations to still include Layout, but otherwise a correct suggestion.
| def get_layout(self, model: object) -> Layout | DefaultLayout: | ||
| """ | ||
| Get the registered layout for a model instance. | ||
| """ | ||
| layout_registry = self.app.config.layout_registry | ||
| model_type = model if isinstance(model, type) else type(model) | ||
|
|
||
| layout_class = None | ||
| for cls in model_type.mro(): | ||
| layout_class = layout_registry.get(cls) | ||
| if layout_class: | ||
| break | ||
|
|
||
| if layout_class is None: | ||
| layout_class = DefaultLayout | ||
|
|
||
| return layout_class(model, self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could probably simplify this by instead relying on a dispatch_method on Framework, which could look something like this:
@dispatch_method()
def get_layout_class(self, model: object) -> type[Layout] | None:
return None
...
@Framework.predicate(Framework.get_layout_class, name="model", default=None, index=ClassIndex)
def model_predicate(self, model: object) -> type:
return model if isinstance(model, type) else model.__class__which simplifies get_layout to
def get_layout(self, model: object) -> Layout:
"""
Get the registered layout for a model instance.
"""
layout_class = self.app.get_layout_class(model)
if layout_class is None:
layout_class = DefaultLayout
return layout_class(model, self)You could also register a predicate_fallback, so get_layout_class returns DefaultLayout if the predicate doesn't match:
@OrgApp.predicate_fallback(OrgApp.get_view, model_predicate)
def model_not_found(self, model: object) -> type[Layout]:
return DefaultLayout
which simplifies get_layout to
def get_layout(self, model: object) -> Layout:
"""
Get the registered layout for a model instance.
"""
layout_class = self.app.get_layout_class(model)
assert layout_class is not None
return layout_class(model, self)and lets you override the fallback in TownApp with its own DefaultLayout.
You then also no longer need a registry for the LayoutAction, you instead register with the dispatch method:
def perform(self, layout: type[Layout], app_class: type[Framework]) -> None:
app_class.get_layout_class.register(layout, model=self.model)|
Thank your for your feedback @BreathingFlesh regarding styling. Latest look
|
Co-authored-by: David Salvisberg <[email protected]>
Co-authored-by: David Salvisberg <[email protected]>




Search: Add breadcrumbs to search results in order to provide more context
TYPE: Feature
LINK: ogc-2880