Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ae72f66
Adds breadcrumbs to search resutls for Topic and News
Tschuppi81 Jan 6, 2026
0ece469
Adds breadcrumbs to search resutls for Person and User
Tschuppi81 Jan 6, 2026
6cc9cb1
Adds breadcrumbs to search resutls for Files and highlights it on the…
Tschuppi81 Jan 6, 2026
713a697
Fix town import in org
Tschuppi81 Jan 8, 2026
1d0b009
Adds breadcrumbs to search resutls for forms
Tschuppi81 Jan 8, 2026
bd1b9d2
Adds breadcrumbs to search resutls for image sets
Tschuppi81 Jan 8, 2026
d32a3d6
Adds breadcrumbs to search resutls for resources
Tschuppi81 Jan 8, 2026
7e0f149
Adds breadcrumbs to search resutls for Event/Occurrence
Tschuppi81 Jan 8, 2026
8ecbaff
Remove print statement
Tschuppi81 Jan 8, 2026
502a4cc
Adds breadcrumbs to search resutls for Ticket
Tschuppi81 Jan 8, 2026
80facd9
Adds breadcrumbs to search resutls for Directory and DirectoryEntry
Tschuppi81 Jan 8, 2026
ce6a0c9
Adds breadcrumbs to search resutls for RIS models
Tschuppi81 Jan 8, 2026
7f971d5
Adjust tests
Tschuppi81 Jan 9, 2026
104a248
Fix wrong model registration
Tschuppi81 Jan 9, 2026
b96c10f
Merge master
Tschuppi81 Jan 9, 2026
c74f7c3
Move styles from template to stylesheet
Tschuppi81 Jan 9, 2026
56b3905
Adjust search preview color and font style
Tschuppi81 Jan 9, 2026
2026859
Adds layout for MeetingItem
Tschuppi81 Jan 13, 2026
baa55ab
Get rid of z-index for breadcrumbs
Tschuppi81 Jan 13, 2026
8952372
Styling improvements
Tschuppi81 Jan 13, 2026
35e646e
Get rid of z-index for breadcrumbs
Tschuppi81 Jan 13, 2026
2a354c7
Update src/onegov/org/layout.py
Tschuppi81 Jan 13, 2026
7f19e29
Link meeting items to meeting with anchor
Tschuppi81 Jan 13, 2026
a9d6c18
Merge branch 'feature/ogc-2880-search-results-with-path' of github.co…
Tschuppi81 Jan 13, 2026
73ae86d
Fix layout condition for events
Tschuppi81 Jan 13, 2026
b26baad
Fix lost link color transition
Tschuppi81 Jan 13, 2026
e4cc346
Improve styling
Tschuppi81 Jan 13, 2026
e2006d1
Merge branch 'feature/ogc-2880-search-results-with-path' of github.co…
Tschuppi81 Jan 13, 2026
c531b26
Adjust styling
Tschuppi81 Jan 15, 2026
0c8c712
Improve link to meeting item using fragment
Tschuppi81 Jan 15, 2026
325f9d2
Undo rename PageLayout
Tschuppi81 Jan 15, 2026
1aa413e
Undo rename PageLayout in agency
Tschuppi81 Jan 15, 2026
f7fe90c
makes sense
Tschuppi81 Jan 15, 2026
2314336
Remove falsly added file
Tschuppi81 Jan 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/onegov/org/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class OrgApp(Framework, LibresIntegration, SearchApp, MapboxApp,
event_search_widget = directive(directives.EventSearchWidgetAction)
settings_view = directive(directives.SettingsView)
boardlet = directive(directives.Boardlet)
layout = directive(directives.Layout)

#: cronjob settings
send_ticket_statistics = True
Expand Down
27 changes: 27 additions & 0 deletions src/onegov/org/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,30 @@ def perform( # type:ignore[override]
'order': self.order,
'icon': self.icon,
}


class Layout(Action):
"""
Registers a layout for a model. This is used to show breadcrumbs
for search results.
"""

config = {
'layout_registry': dict
}

def __init__(self, model: type) -> None:
self.model = model

def identifier( # type:ignore[override]
self,
layout_registry: dict[type, Layout]
) -> str:
return str(self.model)

def perform( # type:ignore[override]
self,
layout: Layout,
layout_registry: dict[type, Layout]
Comment on lines +313 to +320
Copy link

Copilot AI Jan 13, 2026

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.

Suggested change
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]]

Copilot uses AI. Check for mistakes.
Copy link
Member

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.

) -> None:
layout_registry[self.model] = layout
2 changes: 1 addition & 1 deletion src/onegov/org/exports/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from onegov.org.models import Export
from onegov.town6 import _
from onegov.org import _


from typing import Any, TYPE_CHECKING
Expand Down
52 changes: 49 additions & 3 deletions src/onegov/org/layout.py
Copy link
Member

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.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from onegov.org import _
from onegov.org import utils
from onegov.org.exports.base import OrgExport
from onegov.org.models import CitizenDashboard
from onegov.org.models import CitizenDashboard, GeneralFile
from onegov.org.models import ExportCollection, Editor
from onegov.org.models import GeneralFileCollection
from onegov.org.models import ImageFile
Expand Down Expand Up @@ -289,6 +289,21 @@ def get_link(locale: str) -> str:
for locale in sorted(self.app.locales)
]

@cached_property
def files_url(self) -> str:
""" Returns the url to the files view. """
url = self.request.link(
GeneralFileCollection(self.request.session)
)
return self.csrf_protected_url(url)

def files_url_with_anchor(self, file: GeneralFile | None) -> str:
""" Returns the url to the files view including anchor. """
if file is None:
return self.files_url

return f'{self.files_url}#{file.name}'

@cached_property
def file_upload_url(self) -> str:
""" Returns the url to the file upload action. """
Expand Down Expand Up @@ -1329,6 +1344,21 @@ def editbar_links(self) -> list[Link | LinkGroup] | None:
return None


class FormDefinitionLayout(DefaultLayout):

@property
def forms_url(self) -> str:
return self.request.class_link(FormCollection)

@cached_property
def breadcrumbs(self) -> list[Link]:
return [
Link(_('Homepage'), self.homepage_url),
Link(_('Forms'), self.forms_url),
Link(self.model.title, self.request.link(self.model))
]


class SurveySubmissionWindowLayout(DefaultLayout):
@cached_property
def breadcrumbs(self) -> list[Link]:
Expand Down Expand Up @@ -1761,7 +1791,8 @@ def breadcrumbs(self) -> list[Link]:
return [
Link(_('Homepage'), self.homepage_url),
Link(_('Tickets'), get_current_tickets_url(self.request)),
Link(self.model.number, '#')
Link(self.model.number, self.request.link(
TicketCollection(self.request.session).by_id(self.model.id)))
]

@cached_property
Expand Down Expand Up @@ -3427,7 +3458,9 @@ def og_description(self) -> str:
def breadcrumbs(self) -> list[Link]:
return [
Link(_('Homepage'), self.homepage_url),
Link(_('Directories'), '#')
Link(_('Directories'), self.request.class_link(
DirectoryCollection
)),
]

@cached_property
Expand All @@ -3451,6 +3484,19 @@ def editbar_links(self) -> list[Link | LinkGroup] | None:
return None


class DirectoryLayout(DefaultLayout):

@cached_property
def breadcrumbs(self) -> list[Link]:
return [
Link(_('Homepage'), self.homepage_url),
Link(_('Directories'), self.request.class_link(
DirectoryCollection
)),
Link(self.model.title, self.request.link(self.model))
]


class DirectoryEntryMixin:

request: OrgRequest
Expand Down
19 changes: 19 additions & 0 deletions src/onegov/org/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from onegov.core.request import CoreRequest
from onegov.core.security import Private
from onegov.core.utils import normalize_for_url
from onegov.org.layout import DefaultLayout, Layout
from onegov.org.models import News, TANAccessCollection, Topic
from onegov.page import Page, PageCollection
from onegov.user import User
Expand Down Expand Up @@ -283,3 +284,21 @@ def analytics_provider(self) -> AnalyticsProvider | None:
if name := self.app.org.analytics_provider_name:
return self.app.available_analytics_providers.get(name)
return None

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)
Comment on lines +288 to +304
Copy link
Member

@Daverball Daverball Jan 14, 2026

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)

Loading
Loading