""" The onegov org collection of images uploaded to the site. """
from __future__ import annotations
from datetime import date
from markupsafe import Markup
from morepath import redirect
from morepath.request import Response
from onegov.core.security import Public, Private, Secret
from onegov.core.utils import linkify, normalize_for_url
from onegov.event import Occurrence, OccurrenceCollection
from onegov.form.errors import (InvalidFormSyntax, MixedTypeError,
DuplicateLabelError)
from onegov.org import _, OrgApp
from onegov.org.elements import Link
from onegov.core.elements import Link as CoreLink
from onegov.org.forms import ExportForm, EventImportForm
from onegov.org.forms.event import EventConfigurationForm
from onegov.org.layout import OccurrenceLayout, OccurrencesLayout
from onegov.org.views.utils import show_tags, show_filters
from onegov.ticket import TicketCollection
from operator import itemgetter
from sedate import as_datetime, replace_timezone
from typing import NamedTuple, TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Mapping
from onegov.core.types import JSON_ro, RenderData
from onegov.event.collections.occurrences import DateRange
from onegov.org.request import OrgRequest
from webob import Response as BaseResponse
[docs]
class Filter(NamedTuple):
[docs]
def get_filters(
request: OrgRequest,
self: OccurrenceCollection,
keyword_counts: Mapping[str, Mapping[str, int]],
view_name: str = ''
) -> list[Filter]:
filters = []
radio_fields = {
f.id for f in request.app.org.event_filter_fields if f.type == 'radio'
}
def get_count(keyword: str, value: str) -> int:
return keyword_counts.get(keyword, {}).get(value, 0)
def link_title(field_id: str, value: str) -> str:
count = keyword_counts.get(field_id, {}).get(value, 0)
return f'{value} ({count})'
for keyword, title, values in self.available_filters(sort_choices=False):
selected = self.filter_keywords.getall(keyword)
options = tuple(
CoreLink(
text=link_title(keyword, value),
active=value in selected,
url=request.link(
self.for_toggled_keyword_value(
keyword,
value,
singular=keyword in radio_fields,
),
name=view_name
),
rounded=keyword in radio_fields
) for value in values if get_count(keyword, value)
)
if not options:
continue
filters.append(Filter(title=title, tags=options))
return filters
@OrgApp.html(model=OccurrenceCollection, template='occurrences.pt',
permission=Public)
[docs]
def view_occurrences(
self: OccurrenceCollection,
request: OrgRequest,
layout: OccurrencesLayout | None = None
) -> RenderData:
""" View all occurrences of all events. """
filters = None
tags = None
filter_type = request.app.org.event_filter_type
filter_config = request.app.org.event_filter_configuration
layout = layout or OccurrencesLayout(self, request)
custom_tags = bool(request.app.custom_event_tags)
used_tags = sorted((
(tag, (tag if custom_tags else request.translate(_(tag))))
for tag in self.used_tags
), key=itemgetter(1))
if (
filter_type in ('filters', 'tags_and_filters')
and filter_config.get('keywords', None)
):
self.set_event_filter_configuration(filter_config)
self.set_event_filter_fields(request.app.org.event_filter_fields)
keyword_counts = self.keyword_counts()
filters = get_filters(request, self, keyword_counts)
if filter_type in ('tags', 'tags_and_filters'):
tags = [
Link(
text=translation + f' ({self.tag_counts[tag]})',
url=request.link(self.for_filter(tag=tag)),
active=tag in self.tags
) for tag, translation in used_tags if self.tag_counts[tag]
]
locations = [
Link(
text=location,
url=request.link(self.for_filter(location=location)),
active=location in self.locations
) for location in sorted(
request.app.org.event_locations,
key=lambda l: normalize_for_url(l)
)
]
range_labels: tuple[tuple[DateRange, str], ...] = (
('today', _('Today')),
('tomorrow', _('Tomorrow')),
('weekend', _('This weekend')),
('week', _('This week')),
('month', _('This month')),
('past', _('Past events')),
)
ranges = [
Link(
text=_('All'),
url=request.link(self.for_filter(start=None, end=None)),
active=not (self.range or self.start or self.end)
)
] + [
Link(
text=label,
url=request.link(
self.for_filter(range=range, start=None, end=None)
),
active=range == self.range
) for range, label in range_labels
]
files = list(request.app.org.event_files)
return {
'active_tags': self.tags,
'add_link': request.link(self, name='new'),
'date_placeholder': date.today().isoformat(),
'end': self.end.isoformat() if self.end else '',
'layout': layout,
'occurrences': self.batch,
'start': self.start.isoformat() if self.start else '',
'ranges': ranges,
'tags': tags,
'files': files,
'filters': filters,
'locations': locations,
'title': _('Events'),
'search_widget': self.search_widget,
'show_tags': show_tags(request),
'show_filters': show_filters(request),
'no_event_link': request.link(
self.for_filter(range='past', start=None, end=None)
),
'header_html': request.app.org.event_header_html,
'footer_html': request.app.org.event_footer_html,
'time_suffix': request.translate(_("o'clock")),
}
@OrgApp.html(model=Occurrence, template='occurrence.pt', permission=Public)
[docs]
def view_occurrence(
self: Occurrence,
request: OrgRequest,
layout: OccurrenceLayout | None = None
) -> RenderData:
""" View a single occurrence of an event. """
layout = layout or OccurrenceLayout(self, request)
today = replace_timezone(as_datetime(date.today()), self.timezone)
occurrences = self.event.occurrence_dates(localize=True)
occurrences = list(filter(lambda x: x >= today, occurrences))
description = linkify(
self.event.description or '').replace('\n', Markup('<br>'))
session = request.session
ticket = TicketCollection(session).by_handler_id(self.event.id.hex)
framed = request.GET.get('framed')
return {
'description': description,
'framed': framed,
'organizer': self.event.organizer,
'organizer_email': self.event.organizer_email,
'organizer_phone': self.event.organizer_phone,
'external_event_url': self.event.external_event_url,
'layout': layout,
'occurrence': self,
'occurrences': occurrences,
'overview': request.class_link(OccurrenceCollection),
'ticket': ticket,
'title': self.title,
'show_tags': show_tags(request),
'show_filters': show_filters(request),
'time_suffix': request.translate(_("o'clock")),
}
@OrgApp.form(model=OccurrenceCollection, name='edit',
template='directory_form.pt', permission=Secret,
form=EventConfigurationForm)
[docs]
def handle_edit_event_filters(
self: OccurrenceCollection,
request: OrgRequest,
form: EventConfigurationForm,
layout: OccurrencesLayout | None = None
) -> RenderData | BaseResponse:
try:
if form.submitted(request):
keywords = (form.keyword_fields.data or '').splitlines()
request.app.org.event_filter_configuration = {
'order': [],
'keywords': keywords
}
request.app.org.event_filter_definition = form.definition.data
request.success(_('Your changes were saved'))
return request.redirect(request.link(self))
elif not request.POST:
# Store the model data on the form
form.definition.data = request.app.org.event_filter_definition
form.keyword_fields.data = '\r\n'.join(
request.app.org.event_filter_configuration.get('keywords', []))
except InvalidFormSyntax as e:
request.warning(
_('Syntax Error in line ${line}', mapping={'line': e.line})
)
except AttributeError:
request.warning(_('Syntax error in form'))
except MixedTypeError as e:
request.warning(
_('Syntax error in field ${field_name}',
mapping={'field_name': e.field_name})
)
except DuplicateLabelError as e:
request.warning(
_('Error: Duplicate label ${label}', mapping={'label': e.label})
)
layout = layout or OccurrencesLayout(self, request)
layout.include_code_editor()
layout.breadcrumbs.append(Link(_('Edit'), '#'))
layout.editbar_links = []
return {
'layout': layout,
'title': _('Edit Event Filter Configuration'),
'form': form,
'form_width': 'large',
'migration': None,
'model': self,
'error': None,
}
@OrgApp.view(model=Occurrence, name='ical', permission=Public)
[docs]
def ical_export_occurence(self: Occurrence, request: OrgRequest) -> Response:
""" Returns the occurrence as ics. """
return Response(
self.as_ical(url=request.link(self)),
content_type='text/calendar',
content_disposition='inline; filename=calendar.ics'
)
@OrgApp.view(model=OccurrenceCollection, name='ical', permission=Public)
[docs]
def ical_export_occurences(
self: OccurrenceCollection,
request: OrgRequest
) -> Response:
""" Returns the occurrences as ics. """
return Response(
self.as_ical(request),
content_type='text/calendar',
content_disposition='inline; filename=calendar.ics'
)
@OrgApp.form(model=OccurrenceCollection, name='export', permission=Public,
form=ExportForm, template='export.pt')
[docs]
def export_occurrences(
self: OccurrenceCollection,
request: OrgRequest,
form: ExportForm,
layout: OccurrencesLayout | None = None
) -> RenderData | BaseResponse:
""" Export the occurrences in various formats. """
layout = layout or OccurrencesLayout(self, request)
layout.breadcrumbs.append(Link(_('Export'), '#'))
layout.editbar_links = None # type:ignore[assignment]
if form.submitted(request):
import_form = EventImportForm()
import_form.request = request
results = import_form.run_export()
return form.as_export_response(results, title=request.translate(_(
'Events'
)))
return {
'layout': layout,
'title': _('Event Export'),
'form': form,
'explanation': _('Exports all future events.')
}
@OrgApp.json(
model=OccurrenceCollection,
name='json',
permission=Public,
open_data=False
)
[docs]
def json_export_occurrences(
self: OccurrenceCollection,
request: OrgRequest
) -> JSON_ro:
""" Returns the occurrences as JSON.
This is used for the senantis.dir.eventsportlet.
"""
query = self.for_filter(
tags=request.params.getall('cat1'), # type:ignore[arg-type]
locations=request.params.getall('cat2') # type:ignore[arg-type]
).query()
limit = request.params.get('max')
if isinstance(limit, str) and limit.isdigit():
query = query.limit(int(limit))
return [
{
'start': occurrence.start.isoformat(),
'title': occurrence.title,
'url': request.link(occurrence),
} for occurrence in query
]
@OrgApp.view(
model=OccurrenceCollection,
name='xml',
permission=Public
)
[docs]
def xml_export_all_occurrences(
self: OccurrenceCollection,
request: OrgRequest
) -> Response:
"""
Returns events as xml.
Url for xml view: ../events/xml
"""
collection = OccurrenceCollection(request.session)
return Response(
collection.as_xml(),
content_type='text/xml',
content_disposition='inline; filename=events.xml'
)
@OrgApp.form(
model=OccurrenceCollection,
name='import',
template='form.pt',
form=EventImportForm,
permission=Private
)
[docs]
def import_occurrences(
self: OccurrenceCollection,
request: OrgRequest,
form: EventImportForm,
layout: OccurrencesLayout | None = None
) -> RenderData | BaseResponse:
if form.submitted(request):
count, errors = form.run_import()
if errors:
request.alert(_(
'The following line(s) contain invalid data: ${lines}',
mapping={'lines': ', '.join(errors)}
))
elif form.dry_run.data:
request.success(_(
'${count} events will be imported',
mapping={'count': count}
))
else:
request.success(_(
'${count} events imported',
mapping={'count': count}
))
return redirect(request.link(self))
layout = layout or OccurrencesLayout(self, request)
return {
'layout': layout,
'callout': _(
'The same format as the export (XLSX) can be used for the '
'import.'
),
'title': _('Import'),
'form': form,
'form_width': 'large',
}