from __future__ import annotations
from functools import cached_property
from datetime import datetime
from dateutil.relativedelta import relativedelta
from purl import URL
from urllib.parse import urlencode
import pytz
from onegov.translator_directory import _
from onegov.core.elements import Block, Link, LinkGroup, Confirm, Intercooler
from onegov.core.utils import linkify
from onegov.town6.layout import DefaultLayout as BaseLayout
from onegov.town6.layout import TicketLayout as TownTicketLayout
from onegov.org.models import Organisation
from onegov.org.utils import get_current_tickets_url
from onegov.translator_directory.collections.documents import \
TranslatorDocumentCollection
from onegov.translator_directory.collections.language import LanguageCollection
from onegov.translator_directory.collections.translator import (
TranslatorCollection,
)
from onegov.translator_directory.constants import (
member_can_see, editor_can_see, translator_can_see,
GENDERS, ADMISSIONS, PROFESSIONAL_GUILDS,
INTERPRETING_TYPES, TIME_REPORT_INTERPRETING_TYPES)
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from collections.abc import Iterable
from decimal import Decimal
from onegov.translator_directory.collections.time_report import (
TimeReportCollection,
)
from onegov.translator_directory.models.language import Language
from markupsafe import Markup
from onegov.translator_directory.models.translator import (
AdmissionState, Gender, Translator)
from onegov.translator_directory.request import TranslatorAppRequest
[docs]
class DefaultLayout(BaseLayout):
[docs]
request: TranslatorAppRequest
@staticmethod
[docs]
def linkify(text: str | None) -> Markup: # type:ignore[override]
return linkify(text)
@staticmethod
@staticmethod
[docs]
def show(self, attribute_name: str) -> bool:
"""Some attributes on the translator are hidden for less privileged
users"""
if self.request.is_admin:
return True
if self.request.is_editor:
return attribute_name in editor_can_see
if self.request.is_member:
return attribute_name in member_can_see
if self.request.is_translator:
return attribute_name in translator_can_see
return False
[docs]
def color_class(self, count: int) -> str | None:
""" Depending how rare a language is offered by translators,
apply a color code using the returned css class
"""
if count <= 5:
return 'text-orange'
return None
@staticmethod
[docs]
class TranslatorLayout(DefaultLayout):
if TYPE_CHECKING:
def __init__(
self,
model: Translator,
request: TranslatorAppRequest
) -> None: ...
[docs]
def translator_data_outdated(self) -> bool:
if self.request.is_translator:
tz = pytz.timezone('Europe/Zurich')
year_ago = datetime.now(tz=tz) - relativedelta(years=1)
if self.model.modified:
return self.model.modified < year_ago
else:
return self.model.created < year_ago
else:
return False
@cached_property
[docs]
def file_collection(self) -> TranslatorDocumentCollection:
return TranslatorDocumentCollection(
self.request.session,
translator_id=self.model.id,
category=None
)
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup] | None:
if self.request.is_admin:
return [
LinkGroup(
title=_('Add'),
links=(
Link(
text=_('Add translator'),
url=self.request.class_link(
TranslatorCollection, name='new'
),
attrs={'class': 'new-person'}
),
)
),
Link(
text=_('Edit'),
url=self.request.link(
self.model, name='edit'
),
attrs={'class': 'edit-link'}
),
Link(
_('Delete'),
self.csrf_protected_url(
self.request.link(self.model)
),
attrs={'class': 'delete-link'},
traits=(
Confirm(
_('Do you really want to delete '
'this translator?'),
_('This cannot be undone.'),
_('Delete translator'),
_('Cancel')
),
Intercooler(
request_method='DELETE',
redirect_after=self.request.class_link(
TranslatorCollection
)
)
)
),
Link(
_('Documents'),
self.request.link(self.file_collection),
attrs={'class': 'documents'}
),
Link(
_('Mail templates'),
url=self.request.link(
self.model, name='mail-templates'
),
attrs={'class': 'envelope'}
),
Link(
_('Add Time Report'),
url=self.request.link(self.model, name='add-time-report'),
attrs={'class': 'plus'},
),
]
elif self.request.is_editor:
return [
Link(
text=_('Edit'),
url=self.request.link(
self.model, name='edit-restricted'
),
attrs={'class': 'edit-link'}
),
Link(
_('Report change'),
self.request.link(self.model, name='report-change'),
attrs={'class': 'report-change'}
),
Link(
_('Add Time Report'),
url=self.request.link(self.model, name='add-time-report'),
attrs={'class': 'plus'},
),
]
elif self.request.is_member:
return [
Link(
_('Add Time Report'),
url=self.request.link(self.model, name='add-time-report'),
attrs={'class': 'plus'},
),
]
elif self.translator_data_outdated():
return [
Link(
_('Report change'),
self.request.link(self.model, name='report-change'),
attrs={'class': 'report-change'}
),
Link(
_('Confirm current data'),
self.request.link(self.model,
name='confirm-current-data'),
attrs={'class': 'accept-link'}
)
]
elif self.request.is_translator:
return [
Link(
_('Report change'),
self.request.link(self.model, name='report-change'),
attrs={'class': 'report-change'}
)
]
return None
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
if self.request.is_translator:
links.append(
Link(
text=_('Personal Information'),
url=self.request.link(self.model)
),
)
else:
links.append(
Link(
text=_('Translators'),
url=self.request.class_link(TranslatorCollection)
),
)
links.append(
Link(
text=self.model.title,
url=self.request.link(self.model)
)
)
return links
[docs]
class EditTranslatorLayout(TranslatorLayout):
@cached_property
[docs]
def title(self) -> str:
return _('Edit translator')
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
return []
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
links.append(Link(_('Edit')))
return links
[docs]
class ReportTranslatorChangesLayout(TranslatorLayout):
@cached_property
[docs]
def title(self) -> str:
return _('Report change')
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
return []
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
links.append(Link(_('Report change')))
return links
[docs]
class ApplyTranslatorChangesLayout(TranslatorLayout):
@cached_property
[docs]
def title(self) -> str:
return _('Report change')
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
return []
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
links.append(Link(_('Apply proposed changes')))
return links
[docs]
class MailTemplatesLayout(TranslatorLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
return [
*super().breadcrumbs,
Link(
text=_('Mail templates'),
url=self.request.link(
self.model, name='mail-templates'
)
)
]
[docs]
class TranslatorCollectionLayout(DefaultLayout):
[docs]
model: TranslatorCollection
@cached_property
[docs]
def title(self) -> str:
return _('Search for translators')
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
return [ # type:ignore[misc]
*super().breadcrumbs,
Link(
text=_('Translators'),
url=self.request.class_link(TranslatorCollection)
)
]
@cached_property
[docs]
def export_link(self) -> str | None:
""" Returns the export link with current filters included, or None """
if not self.request.is_admin:
return None
params = self.request.GET.copy()
# Remove pagination parameter, not needed for export
params.pop('page', None)
# Ensure sorting parameters in the link match the actual state of the
# collection model, handling defaults correctly.
if self.model.order_by != 'last_name':
params['order_by'] = self.model.order_by
else:
# Remove order_by from params if it's the default
params.pop('order_by', None)
if self.model.order_desc:
params['order_desc'] = 'true'
else:
# Remove order_desc from params if it's the default (False)
params.pop('order_desc', None)
base_export_link = self.request.class_link(
TranslatorCollection, name='export'
)
return (
f'{base_export_link}?{urlencode(params, doseq=True)}'
if params else base_export_link
)
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup] | None:
if self.request.is_admin:
return [
LinkGroup(
_('Add'),
links=(
Link(
text=_('Add translator'),
url=self.request.class_link(
TranslatorCollection, name='new'
),
attrs={'class': 'new-person'}
),
)
),
Link(
_('Mail to all translators'),
url=self.request.app.mailto_link,
attrs={'class': 'envelope'},
)
]
elif self.request.is_editor or self.request.is_member:
return []
return None
[docs]
class AddTranslatorLayout(TranslatorCollectionLayout):
@cached_property
[docs]
def title(self) -> str:
return _('Add translator')
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
links.append(Link(_('Add')))
return links
@property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
return []
[docs]
class TranslatorDocumentsLayout(DefaultLayout):
def __init__(self, model: Any, request: TranslatorAppRequest) -> None:
super().__init__(model, request)
request.include('upload')
request.include('prompt')
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
return [ # type:ignore[misc]
*super().breadcrumbs,
Link(
text=_('Translators'),
url=self.request.class_link(TranslatorCollection)
),
Link(
text=self.model.translator.title,
url=self.request.link(self.model.translator)
),
Link(text=_('Documents'))
]
@cached_property
[docs]
def upload_url(self) -> str:
url = URL(self.request.link(self.model, name='upload'))
url = url.query_param('category', self.model.category)
return self.csrf_protected_url(url.as_string())
[docs]
def link_for(self, category: str) -> str:
return self.request.class_link(
self.model.__class__,
{'translator_id': self.model.translator_id, 'category': category}
)
[docs]
class LanguageCollectionLayout(DefaultLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(Link(_('Languages')))
return links
@property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
return [LinkGroup(
_('Add'),
links=(
Link(
text=_('Add language'),
url=self.request.class_link(
LanguageCollection, name='new'
),
attrs={'class': 'new-language'}
),
)
)] if self.request.is_admin else []
[docs]
class LanguageLayout(DefaultLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(
Link(_('Languages'),
url=self.request.class_link(LanguageCollection))
)
return links
[docs]
class EditLanguageLayout(LanguageLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
links.append(Link(self.model.name))
links.append(Link(_('Edit')))
return links
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
if self.request.is_admin:
if not self.model.deletable:
return [
Link(
_('Delete'),
self.csrf_protected_url(
self.request.link(self.model)
),
attrs={'class': 'delete-link'},
traits=(
Block(
_("This language is used and can't be "
"deleted."),
no=_('Cancel')
),
)
),
]
return [
Link(
_('Delete'),
self.csrf_protected_url(
self.request.link(self.model)
),
attrs={'class': 'delete-link'},
traits=(
Confirm(
_('Do you really want to delete '
'this language?'),
_('This cannot be undone.'),
_('Delete language'),
_('Cancel')
),
Intercooler(
request_method='DELETE',
redirect_after=self.request.class_link(
TranslatorCollection
)
)
)
),
]
return []
[docs]
class AddLanguageLayout(LanguageLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
links.append(Link(_('Add')))
return links
@property
[docs]
def editbar_links(self) -> list[Link | LinkGroup]:
return []
[docs]
class AccreditationLayout(DefaultLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(
Link(
text=_('Accreditation'),
url=self.request.link(self.model.ticket)
)
)
return links
[docs]
class RequestAccreditationLayout(DefaultLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(
Link(
text=_('Accreditation'),
url=self.request.class_link(
Organisation, name='request-accreditation'
)
)
)
return links
[docs]
class GrantAccreditationLayout(DefaultLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(
Link(
text=_('Accreditation'),
url=self.request.link(self.model.ticket)
)
)
links.append(Link(_('Grant admission')))
return links
[docs]
class RefuseAccreditationLayout(DefaultLayout):
@property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(
Link(
text=_('Accreditation'),
url=self.request.link(self.model.ticket)
)
)
links.append(Link(_('Refuse admission')))
return links
[docs]
class TimeReportCollectionLayout(DefaultLayout):
if TYPE_CHECKING:
[docs]
model: TimeReportCollection
@cached_property
[docs]
def title(self) -> str:
return _('Time Reports')
@cached_property
[docs]
def breadcrumbs(self) -> list[Link]:
links = super().breadcrumbs
assert isinstance(links, list)
links.append(Link(_('Time Reports')))
return links
[docs]
class TicketLayout(TownTicketLayout):
@cached_property
[docs]
def editbar_links(self) -> list[Link | LinkGroup] | None:
from onegov.translator_directory.models.ticket import (
TimeReportHandler,
)
links = super().editbar_links
if links is None:
return None
if not self.request.is_manager:
return links
if self.model.handler_code != 'TRP':
return links
if self.model.state != 'open':
return links
handler = self.model.handler
if not isinstance(handler, TimeReportHandler):
return links
if not handler.can_delete_time_report(
self.request # type: ignore[arg-type]
):
return links
if handler.time_report is None:
return links
delete_link = Link(
text=_('Delete Time Report'),
url=self.csrf_protected_url(
self.request.link(handler.time_report)
),
attrs={'class': ('ticket-button', 'ticket-delete')},
traits=(
Confirm(
_('Do you really want to delete this time report?'),
_('This cannot be undone.'),
_('Delete time report'),
_('Cancel'),
),
Intercooler(
request_method='DELETE',
redirect_after=get_current_tickets_url(self.request),
),
),
)
links.append(delete_link)
return links