from dataclasses import dataclass
from dectate import Action
from dectate import Composite
from itertools import count
from onegov.core.directives import HtmlHandleFormAction
from typing import cast, Any, ClassVar, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Iterator
from onegov.core.elements import LinkGroup
from onegov.directory.models import DirectoryEntry
from onegov.event.models import Occurrence
from onegov.form import Form
from onegov.org.models import Boardlet as _Boardlet
from onegov.org.request import OrgRequest
from onegov.user import User
from sqlalchemy.orm import Query
from typing import Protocol, TypedDict
from webob import Response
type SettingViewRegistry = dict[
tuple[type, str],
SettingViewMeta,
]
type DirectorySearchWidgetRegistry = dict[
str,
type[RegisteredDirectorySearchWidget[Any]]
]
type EventSearchWidgetRegistry = dict[
str,
type[RegisteredEventSearchWidget]
]
type LinkGroupFactory = Callable[[OrgRequest, User], LinkGroup]
type BoardletKind = Literal['user', 'citizen']
class HomepageWidget(Protocol):
@property
def template(self) -> str: ...
class RegisteredHomepageWidget(HomepageWidget, Protocol):
tag: str
class DirectorySearchWidget[EntryT: DirectoryEntry](Protocol):
@property
def search_query(self) -> Query[EntryT]: ...
def adapt(
self,
query: Query[EntryT]
) -> Query[EntryT]: ...
class RegisteredDirectorySearchWidget[EntryT: DirectoryEntry](
DirectorySearchWidget[EntryT],
Protocol
):
name: str
class EventSearchWidget(Protocol):
@property
def search_query(self) -> Query[Occurrence]: ...
def adapt(
self,
query: Query[Occurrence]
) -> Query[Occurrence]: ...
class RegisteredEventSearchWidget(EventSearchWidget, Protocol):
name: str
class SettingsDict(TypedDict):
name: str
title: str
order: int
icon: str
category: str
class BoardletConfig(TypedDict):
cls: type[_Boardlet]
order: tuple[int, int]
icon: str
@dataclass(frozen=True)
[docs]
class SettingViewAction(Composite):
[docs]
query_classes = [HtmlHandleFormAction, SettingViewMetaAction]
def __init__[RequestT: OrgRequest](
self,
model: type,
name: str,
form: FormFactory[RequestT],
setting: str | None = None,
icon: str | None = None,
order: int = 0,
category: str | None = None,
listed: bool = True,
render: Callable[[Any, RequestT], Response] | None = None,
template: str | None = None,
load: Callable[[RequestT], Any] | None = None,
permission: object | None = None,
internal: bool = False,
pass_model: bool = False,
**predicates: Any,
) -> None:
if listed and (setting is None or icon is None):
raise TypeError(
'Listed setting views require setting and icon metadata'
)
if not listed and (setting is not None or icon is not None):
raise TypeError(
'Unlisted setting views cannot define setting or icon'
)
[docs]
self.category = category
[docs]
self.template = template
[docs]
self.permission = permission
[docs]
self.internal = internal
[docs]
self.pass_model = pass_model
[docs]
self.predicates = predicates
[docs]
def actions[RequestT: OrgRequest](
self,
obj: Callable[[Any, RequestT, Any], Any],
) -> Iterator[tuple[Action, Callable[[Any, RequestT, Any], Any]]]:
yield HtmlHandleFormAction(
model=self.model,
form=self.form,
render=self.render,
template=self.template,
load=self.load,
permission=self.permission,
internal=self.internal,
pass_model=self.pass_model,
name=self.name,
**self.predicates,
), obj
yield SettingViewMetaAction(
model=self.model,
name=self.name,
form=self.form,
setting=self.setting if self.listed else None,
icon=self.icon if self.listed else None,
order=self.order,
category=self.category,
), obj
[docs]
class HomepageWidgetAction(Action):
""" Register a cronjob. """
[docs]
config = {
'homepage_widget_registry': dict
}
def __init__(self, tag: str) -> None:
[docs]
def identifier(
self,
homepage_widget_registry: dict[str, RegisteredHomepageWidget]
) -> str:
return self.tag
[docs]
def perform(
self,
func: Callable[[], HomepageWidget],
homepage_widget_registry: dict[str, RegisteredHomepageWidget]
) -> None:
widget = cast('RegisteredHomepageWidget', func())
widget.tag = self.tag # keep redundantly for ease of access
homepage_widget_registry[self.tag] = widget
[docs]
class ExportAction(Action):
""" Register an export. """
[docs]
config = {
'export_registry': dict
}
def __init__(self, id: str, **kwargs: Any) -> None:
self.kwargs['id'] = id
[docs]
def identifier(
self,
export_registry: dict[str, Any]
) -> str:
return self.id
[docs]
class UserlinkAction(Action):
""" Registers a user link group. """
[docs]
config = {
'linkgroup_registry': list
}
[docs]
counter: ClassVar = count(1)
def __init__(self) -> None:
[docs]
self.name = next(self.counter)
[docs]
def identifier(
self,
linkgroup_registry: list[LinkGroupFactory]
) -> int:
return self.name
[docs]
class Boardlet(Action):
""" Registers a boardlet on the Dashboard. """
[docs]
config = {
'boardlets_registry': lambda: {'user': {}, 'citizen': {}}
}
def __init__(
self,
name: str,
order: tuple[int, int],
icon: str = '',
kind: Literal['user', 'citizen'] = 'user'
) -> None:
assert isinstance(order, tuple) and len(order) == 2, """
The order should consist of two values, a group and an order
within the group.
"""
[docs]
self.boardlet_order = order
[docs]
def identifier(
self,
boardlets_registry: dict[BoardletKind, dict[str, BoardletConfig]]
) -> str:
return f'{self.kind}-{self.name}'