from __future__ import annotations
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 _typeshed import StrOrBytesPath
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__(
self,
model: type,
name: str,
form: FormFactory,
setting: str | None = None,
icon: str | None = None,
order: int = 0,
category: str | None = None,
listed: bool = True,
render: Callable[..., Response] | str | None = None,
template: StrOrBytesPath | None = None,
load: Callable[..., Any] | str | None = None,
permission: object | str | 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(
self,
obj: Callable[..., Any],
) -> Iterator[tuple[Action, Callable[..., 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( # type:ignore[override]
self,
homepage_widget_registry: dict[str, RegisteredHomepageWidget]
) -> str:
return self.tag
[docs]
def perform( # type:ignore[override]
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( # type:ignore[override]
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( # type:ignore[override]
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]
def identifier( # type:ignore[override]
self,
boardlets_registry: dict[BoardletKind, dict[str, BoardletConfig]]
) -> str:
return f'{self.kind}-{self.name}'