from __future__ import annotations
from uuid import uuid4
from onegov.core.collection import GenericCollection
from onegov.core.orm import Base
from onegov.core.orm.mixins import (
ContentMixin, TimestampMixin, dict_property, meta_property)
from onegov.core.orm.types import UUID
from onegov.core.utils import normalize_for_url
from onegov.form import FormCollection
from onegov.reservation import ResourceCollection
from onegov.org.i18n import _
from onegov.org.models import AccessExtension
from onegov.org.observer import observes
from onegov.search import SearchableContent
from sqlalchemy import Column, Text
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
import uuid
from sqlalchemy.orm import Query, Session
from typing import Self
[docs]
class ExternalLink(Base, ContentMixin, TimestampMixin, AccessExtension,
SearchableContent):
"""
An Object appearing in some other collection
that features a lead and text but points to some external url.
"""
[docs]
__tablename__ = 'external_links'
[docs]
__mapper_args__ = {
'polymorphic_on': 'member_of'
}
[docs]
fts_type_title = _('External Link')
[docs]
fts_properties = {
'title': {'type': 'localized', 'weight': 'A'},
'lead': {'type': 'localized', 'weight': 'B'},
}
[docs]
id: Column[uuid.UUID] = Column(
UUID, # type:ignore[arg-type]
primary_key=True,
default=uuid4
)
[docs]
title: Column[str] = Column(Text, nullable=False)
[docs]
url: Column[str] = Column(Text, nullable=False)
[docs]
page_image: dict_property[str | None] = meta_property()
# FIXME: should this actually be nullable?
#: The collection name this model should appear in
[docs]
member_of: Column[str | None] = Column(Text, nullable=True)
# TODO: Stop passing title (and maybe even to) as url parameters.
[docs]
group: Column[str | None] = Column(Text, nullable=True)
#: The normalized title for sorting
[docs]
order: Column[str] = Column(Text, nullable=False, index=True)
[docs]
lead: dict_property[str | None] = meta_property()
@observes('title')
[docs]
def title_observer(self, title: str) -> None:
self.order = normalize_for_url(title)
[docs]
class ExternalResourceLink(ExternalLink):
[docs]
__mapper_args__ = {
'polymorphic_identity': 'ResourceCollection'
}
[docs]
fts_type_title = _('Resources')
[docs]
class ExternalLinkCollection(GenericCollection[ExternalLink]):
[docs]
supported_collections = {
'form': FormCollection,
'resource': ResourceCollection
}
def __init__(
self,
session: Session,
member_of: str | None = None,
group: str | None = None,
type: str | None = None
):
super().__init__(session)
[docs]
self.member_of = member_of
@staticmethod
[docs]
def translatable_name(model_class: type[object]) -> str:
""" Most collections have a base model whose name can be guessed
from the collection name. """
name = model_class.__name__.lower()
name = name.replace('collection', '').rstrip('s')
return f'{name.capitalize()}s'
@classmethod
[docs]
def collection_by_name(cls) -> dict[str, type[GenericCollection[Any]]]:
return {m.__name__: m for m in cls.supported_collections.values()}
@property
[docs]
def model_class(self) -> type[ExternalLink]:
if self.member_of is None:
return ExternalLink
return ExternalLink.get_polymorphic_class(self.member_of, ExternalLink)
@classmethod
[docs]
def target(
cls,
external_link: ExternalLink
) -> type[GenericCollection[Any]]:
assert external_link.member_of is not None
return cls.collection_by_name()[external_link.member_of]
[docs]
def query(self) -> Query[ExternalLink]:
query = super().query()
if self.group:
query = query.filter_by(group=self.group)
return query
@classmethod
[docs]
def for_model(
cls,
session: Session,
model_class: type[FormCollection | ResourceCollection],
**kwargs: Any
) -> Self:
""" It would be better to use the tablename, but the collections do
not always implement the property model_class. """
assert model_class in cls.supported_collections.values()
return cls(session, member_of=model_class.__name__, **kwargs)