from collections import defaultdict
import uuid
from datetime import date, datetime
from sqlalchemy import false
from onegov.core.elements import BackLink, Confirm, Intercooler, Link
from onegov.core.security import Private
from onegov.core.templates import render_template
from onegov.core.utils import module_path
from onegov.core.utils import normalize_for_filename
from onegov.pas import _
from onegov.pas import PasApp
from onegov.pas.collections import (
AttendenceCollection,
PASParliamentarianCollection,
PresidentialAllowanceCollection,
SettlementRunCollection,
)
from onegov.pas.custom import (
has_user_set_abschluss_for_commission,
validate_attendance_date,
notify_admins_finalized,
)
from onegov.pas.forms import AttendenceAddCommissionBulkForm, AttendenceAddForm
from onegov.pas.forms import AttendenceAddPlenaryForm
from onegov.pas.forms import AttendenceForm
from onegov.pas.forms.attendence import AttendenceAddFileStudyBulkForm
from onegov.pas.forms.attendence import AttendenceCommissionBulkEditForm
from onegov.pas.forms.attendence import AttendencePlenaryBulkEditForm
from onegov.pas.layouts import AttendenceCollectionLayout
from onegov.pas.layouts import AttendenceLayout
from onegov.pas.models import Attendence
from onegov.pas.models import Change
from onegov.pas.models import PresidentialAllowance
from onegov.pas.models import SettlementRun
from onegov.pas.models.attendence import TYPES
from onegov.pas.models.commission_membership import PASCommissionMembership
from onegov.pas.utils import (
get_attendance_parliamentarians,
)
from onegov.user import User
from weasyprint import CSS, HTML # type: ignore[import-untyped]
from uuid import UUID
from webob import Response
from webob.exc import HTTPNotFound
from weasyprint.text.fonts import ( # type: ignore[import-untyped]
FontConfiguration,
)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from onegov.core.types import RenderData
from onegov.town6.request import TownRequest
from onegov.pas.request import PasRequest
@PasApp.html(
model=AttendenceCollection,
template='attendences.pt',
permission=Private
)
[docs]
def view_attendences(
self: AttendenceCollection,
request: PasRequest
) -> RenderData:
layout = AttendenceCollectionLayout(self, request)
attendences = self.query_for_current_user(request)
groups: dict[str, list[Attendence]] = defaultdict(list)
for a in attendences:
if a.bulk_edit_id:
groups[str(a.bulk_edit_id)].append(a)
bulk_edit_groups = sorted(
groups.values(),
key=lambda g: max(a.modified or a.created for a in g),
reverse=True,
)
attendences_sorted = sorted(
attendences,
key=lambda a: a.created or a.modified or datetime.min,
reverse=True,
)
attendence_ids = {str(a.id) for a in attendences_sorted}
creators: dict[str, str] = {}
if attendence_ids:
for change in request.session.query(Change).filter_by(
model='attendence', action='add'
):
change_data = change.changes or {}
attendence_id = change_data.get('id')
if (
isinstance(attendence_id, str)
and attendence_id in attendence_ids
and change.user
):
creators.setdefault(attendence_id, change.user)
type_filters = [
Link(
text=request.translate(_('All')),
active=self.type is None,
url=request.link(
self.for_filter(
settlement_run_id=self.settlement_run_id,
date_from=self.date_from,
date_to=self.date_to,
type=None,
parliamentarian_id=self.parliamentarian_id,
commission_id=self.commission_id,
party_id=self.party_id,
plenary_date=self.plenary_date,
)
),
)
]
for key, label in TYPES.items():
if not request.is_admin and key == 'plenary':
continue
type_filters.append(
Link(
text=request.translate(label),
active=self.type == key,
url=request.link(
self.for_filter(
settlement_run_id=self.settlement_run_id,
date_from=self.date_from,
date_to=self.date_to,
type=key,
parliamentarian_id=self.parliamentarian_id,
commission_id=self.commission_id,
party_id=self.party_id,
plenary_date=self.plenary_date,
)
),
)
)
settlement_runs = (
SettlementRunCollection(request.session).query().all()
)
run_filters = [
Link(
text=request.translate(_('All')),
active=self.settlement_run_id is None,
url=request.link(
self.for_filter(
settlement_run_id='all',
date_from=self.date_from,
date_to=self.date_to,
type=self.type,
parliamentarian_id=self.parliamentarian_id,
commission_id=self.commission_id,
party_id=self.party_id,
plenary_date=self.plenary_date,
)
),
)
]
for run in settlement_runs:
run_id = str(run.id)
run_filters.append(
Link(
text=run.name,
active=self.settlement_run_id == run_id,
url=request.link(
self.for_filter(
settlement_run_id=run_id,
date_from=self.date_from,
date_to=self.date_to,
type=self.type,
parliamentarian_id=self.parliamentarian_id,
commission_id=self.commission_id,
party_id=self.party_id,
plenary_date=self.plenary_date,
)
),
)
)
plenary_filters: list[Link] = []
if request.is_admin:
plenary_dates_query = (
request.session.query(Attendence.date)
.filter(Attendence.type == 'plenary')
.order_by(Attendence.date.desc())
.distinct()
)
plenary_filters = [
Link(
text=request.translate(_('All')),
active=self.plenary_date is None,
url=request.link(
self.for_filter(
settlement_run_id=self.settlement_run_id,
date_from=self.date_from,
date_to=self.date_to,
type=self.type,
parliamentarian_id=(self.parliamentarian_id),
commission_id=self.commission_id,
party_id=self.party_id,
plenary_date=None,
)
),
)
]
for d, in plenary_dates_query:
plenary_filters.append(
Link(
text=layout.format_date(d, 'date'),
active=self.plenary_date == d,
url=request.link(
self.for_filter(
settlement_run_id=(self.settlement_run_id),
date_from=self.date_from,
date_to=self.date_to,
type=self.type,
parliamentarian_id=(self.parliamentarian_id),
commission_id=(self.commission_id),
party_id=self.party_id,
plenary_date=d,
)
),
)
)
allowance_q = PresidentialAllowanceCollection(
request.session,
settlement_run_id=uuid.UUID(self.settlement_run_id)
if self.settlement_run_id else None,
).query()
if request.is_parliamentarian:
parl = request.current_parliamentarian
if parl:
allowance_q = allowance_q.filter(
PresidentialAllowance.parliamentarian_id
== parl.id
)
else:
allowance_q = allowance_q.filter(false())
allowances = allowance_q.all()
edit_links: dict[uuid.UUID, str] = {}
editable_groups: dict[uuid.UUID, bool] = {}
for a in attendences_sorted:
group_key = a.bulk_edit_id or a.id
if group_key not in editable_groups:
editable_groups[group_key] = request.can_edit_attendence(a)
if not editable_groups[group_key]:
continue
if a.bulk_edit_id:
name = (
'edit-plenary-bulk-attendences'
if a.type == 'plenary'
else 'edit-commission-bulk-attendences'
)
edit_links[a.id] = request.link(a, name)
else:
edit_links[a.id] = request.link(a)
return {
'add_link': request.link(self, name='new'),
'layout': layout,
'attendences': attendences_sorted,
'creators': creators,
'edit_links': edit_links,
'title': layout.title,
'bulk_edit_groups': [
group for group in bulk_edit_groups if group[0].id in edit_links
],
'bulk_pdf_links': {
group[0].id: request.link(group[0], name='bulk-attendence-pdf')
for group in bulk_edit_groups
if group[0].id in edit_links
},
'can_edit_attendences': bool(edit_links),
'allowances': allowances,
'filters': {
'settlement_run': run_filters,
'type': type_filters,
'plenary_session': plenary_filters,
},
}
@PasApp.view(
model=Attendence,
name='bulk-attendence-pdf',
permission=Private,
request_method='GET',
)
[docs]
def view_bulk_attendence_pdf(
self: Attendence,
request: PasRequest,
) -> Response:
if not self.bulk_edit_id or not request.can_edit_attendence(self):
raise HTTPNotFound()
attendances = (
request.session.query(Attendence)
.filter(
Attendence.bulk_edit_id == self.bulk_edit_id,
Attendence.type == self.type,
)
.order_by(Attendence.parliamentarian_id)
.all()
)
date_label = self.date.strftime('%d.%m.%Y')
html = render_template(
'bulk_attendence_pdf.pt',
request,
{
'attendances': attendances,
'date_label': date_label,
'duration': self.duration,
'type_label': request.translate(_(self.type_label)),
'commission': self.commission.title if self.commission else None,
},
)
font_config = FontConfiguration()
css_path = module_path(
'onegov.pas', 'views/templates/bulk_attendence_pdf.css'
)
with open(css_path) as css_file:
css = CSS(string=css_file.read())
logo_path = module_path('onegov.agency', 'static/logos/canton-zg-bw.svg')
logo_css = CSS(
string=f"""
@page {{
@top-left {{
content: url('file://{logo_path}');
width: 3cm;
}}
}}
"""
)
pdf = HTML(string=str(html)).write_pdf(
stylesheets=[css, logo_css],
font_config=font_config,
)
filename = normalize_for_filename(f'Massenbuchung_{self.date.isoformat()}')
return Response(
pdf,
content_type='application/pdf',
content_disposition=f'attachment; filename={filename}.pdf',
)
@PasApp.form(
model=AttendenceCollection,
name='new',
template='form.pt',
form=AttendenceAddForm
)
[docs]
def add_attendence(
self: AttendenceCollection,
request: PasRequest,
form: AttendenceAddForm
) -> RenderData | Response:
request.include('custom')
if form.submitted(request):
if form.date.data:
if error := validate_attendance_date(
request.session, form.date.data
):
request.alert(error)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': _('New attendence'),
'form': form,
'form_width': 'large',
}
if not request.is_admin:
if (
form.parliamentarian_id.data
and form.commission_id.data
and form.date.data
and has_user_set_abschluss_for_commission(
request.session,
form.parliamentarian_id.data,
form.commission_id.data,
form.date.data,
)
):
request.alert(
_(
'Cannot book attendance - abschluss '
'already set for this settlement run'
)
)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': _('New attendence'),
'form': form,
'form_width': 'large',
}
attendence = self.add(**form.get_useful_data())
Change.add(request, 'add', attendence)
request.success(_('Added a new attendence'))
return request.redirect(request.link(attendence))
layout = AttendenceCollectionLayout(self, request)
layout.breadcrumbs.append(Link(_('New'), '#'))
layout.edit_mode = True
return {
'layout': layout,
'title': _('New attendence'),
'form': form,
'form_width': 'large'
}
[docs]
def handle_commission_bulk_add(
self: AttendenceCollection,
request: PasRequest,
form: AttendenceAddCommissionBulkForm,
title: str,
) -> RenderData | Response:
""" Shared handler for the bulk add forms which are bound to a single
commission (commission sessions and file studies).
"""
request.include('custom')
if not request.is_admin and not request.has_commission_president_role:
request.alert(_('Only admins can add bulk attendance records.'))
return request.redirect(request.class_link(AttendenceCollection))
if form.submitted(request):
if form.date.data:
if error := validate_attendance_date(
request.session, form.date.data
):
request.alert(error)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': title,
'form': form,
'form_width': 'large',
}
data = form.get_useful_data()
if raw_parl_ids := request.POST.getall('parliamentarian_id'):
if not request.is_admin and form.commission_id.data:
blocked_parls: list[str] = []
commission_id = form.commission_id.data
user = (
request.session.query(User)
.filter_by(username=request.identity.userid)
.first()
)
today = date.today()
led_commission_ids = set()
if user and user.parliamentarian: # type: ignore[attr-defined]
led_commission_ids = {
membership.commission_id
for membership in (
user.parliamentarian.commission_memberships_on( # type: ignore[attr-defined]
on_date=today,
role='president',
)
)
}
valid_membership_ids = set()
if commission_id in led_commission_ids:
valid_membership_ids = {
membership.parliamentarian_id
for membership in request.session.query(
PASCommissionMembership
).filter_by(commission_id=commission_id)
if membership.is_active_on(today)
}
for parl_id in raw_parl_ids:
if not isinstance(parl_id, str):
blocked_parls.append(_('a selected parliamentarian'))
break
try:
pid = UUID(parl_id)
except ValueError:
blocked_parls.append(_('a selected parliamentarian'))
break
if commission_id not in led_commission_ids:
blocked_parls.append(_('the selected commission'))
break
assert commission_id is not None
if pid not in valid_membership_ids:
blocked_parls.append(_('a selected parliamentarian'))
break
if (
form.date.data
and has_user_set_abschluss_for_commission(
request.session,
pid,
commission_id,
form.date.data,
)
):
parl = PASParliamentarianCollection(request.app).by_id(
pid
)
if parl:
blocked_parls.append(parl.title)
if blocked_parls:
request.alert(
_(
'Cannot book attendance for the selected '
'commission or parliamentarian.'
)
)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': title,
'form': form,
'form_width': 'large',
}
data.pop('parliamentarian_id', None)
bulk_edit_id = uuid.uuid4()
notify_attendence = None
for parliamentarian_id in raw_parl_ids:
attendence = self.add(
parliamentarian_id=parliamentarian_id,
**data,
)
attendence.bulk_edit_id = bulk_edit_id
Change.add(request, 'add', attendence)
if attendence.abschluss and notify_attendence is None:
notify_attendence = attendence
if notify_attendence is not None:
notify_admins_finalized(request, notify_attendence)
else:
request.warning(_('No parliamentarians selected'))
return request.redirect(request.class_link(AttendenceCollection))
request.success(_('Added session'))
return request.redirect(request.link(self))
layout = AttendenceCollectionLayout(self, request)
layout.breadcrumbs.append(Link(title, '#'))
layout.edit_mode = True
return {
'layout': layout,
'title': title,
'form': form,
'form_width': 'large',
}
@PasApp.form(
model=AttendenceCollection,
name='new-commission-bulk',
template='form.pt',
permission=Private,
form=AttendenceAddCommissionBulkForm
)
[docs]
def add_bulk_attendence(
self: AttendenceCollection,
request: PasRequest,
form: AttendenceAddCommissionBulkForm
) -> RenderData | Response:
return handle_commission_bulk_add(
self, request, form, _('New commission session (bulk)')
)
@PasApp.form(
model=AttendenceCollection,
name='new-file-study-bulk',
template='form.pt',
permission=Private,
form=AttendenceAddFileStudyBulkForm
)
[docs]
def add_file_study_bulk_attendence(
self: AttendenceCollection,
request: PasRequest,
form: AttendenceAddFileStudyBulkForm
) -> RenderData | Response:
return handle_commission_bulk_add(
self, request, form, _('New file study (bulk)')
)
@PasApp.form(
model=Attendence,
name='edit-plenary-bulk-attendences',
template='form.pt',
permission=Private,
form=AttendencePlenaryBulkEditForm
)
[docs]
def edit_plenary_bulk_attendence(
self: Attendence,
request: TownRequest,
form: AttendencePlenaryBulkEditForm
) -> RenderData | Response:
request.include('custom')
if not request.is_admin:
request.alert(
_('You do not have permission to edit plenary sessions.')
)
return request.redirect(request.class_link(AttendenceCollection))
all_parliamentarians = [
p.id for p in get_attendance_parliamentarians(request)
]
if form.submitted(request):
# Check if attendance date is in a closed settlement run
if form.date.data:
if error := validate_attendance_date(
request.session, form.date.data
):
request.alert(error)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': _('Edit bulk: plenary session'),
'form': form,
'form_width': 'large'
}
data = form.get_useful_data()
parl_ids = data.pop('parliamentarian_id', ())
collection = AttendenceCollection(request.session)
unselected_parliamentarians = [
pid for pid in all_parliamentarians
if pid not in parl_ids
]
for parliamentarian in unselected_parliamentarians:
if attendence := collection.query().filter(
Attendence.parliamentarian_id == parliamentarian,
Attendence.bulk_edit_id == form.bulk_edit_id.data,
).first():
Change.add(request, 'delete', attendence)
collection.delete(attendence)
for parliamentarian_id in parl_ids:
attendence = collection.query().filter(
Attendence.parliamentarian_id == parliamentarian_id,
Attendence.bulk_edit_id == form.bulk_edit_id.data,
).first()
if attendence:
form.populate_obj(attendence)
Change.add(request, 'edit', attendence)
else:
attendence = collection.add(
parliamentarian_id=parliamentarian_id, **data
)
Change.add(request, 'add', attendence)
request.success(_('Edited attendences'))
return request.redirect(request.class_link(AttendenceCollection))
elif not request.POST:
form.process(obj=self)
layout = AttendenceCollectionLayout(self, request)
layout.breadcrumbs = [
Link(_('Homepage'), layout.homepage_url),
Link(_('Attendences'), request.class_link(AttendenceCollection)),
Link(_('Edit session'), '#')
]
layout.edit_mode = True
layout.editmode_links[1] = BackLink(
attrs={'class': 'cancel-link'},
)
layout.editmode_links.append(
Link(
text=_('Delete'),
url=layout.csrf_protected_url(
request.link(self, name='delete-attendences')),
traits=(
Confirm(
_(
'Do you really want to delete this bulk edit?'
),
_('This cannot be undone.'),
_('Delete Attendencess'),
_('Cancel')
),
Intercooler(
request_method='DELETE',
redirect_after=request.class_link(AttendenceCollection)
)),
attrs={'class': 'delete-link'},
)
)
return {
'layout': layout,
'title': _('Edit bulk: plenary session'),
'form': form,
'form_width': 'large'
}
@PasApp.form(
model=Attendence,
name='edit-commission-bulk-attendences',
template='form.pt',
permission=Private,
form=AttendenceCommissionBulkEditForm
)
[docs]
def edit_commission_bulk_attendence(
self: Attendence,
request: PasRequest,
form: AttendenceCommissionBulkEditForm
) -> RenderData | Response:
request.include('custom')
if not self.bulk_edit_id or not request.can_edit_attendence(self):
request.alert(_('Insufficient permissions.'))
return request.redirect(request.class_link(AttendenceCollection))
type_label = request.translate(self.type_label)
title = _('Edit bulk: ${type}', mapping={'type': type_label})
if form.submitted(request):
if not request.is_admin:
assert form.date.data is not None
if (
form.bulk_edit_id.data != str(self.bulk_edit_id)
or form.commission_id.data != self.commission_id
or not all(
request.can_edit_commission_attendance(
form.date.data, self.commission_id, person_id
)
for person_id in (form.parliamentarian_id.data or [])
)
):
request.alert(_('Insufficient permissions.'))
return request.redirect(
request.class_link(AttendenceCollection)
)
if form.date.data:
if error := validate_attendance_date(
request.session, form.date.data
):
request.alert(error)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': title,
'form': form,
'form_width': 'large'
}
data = form.get_useful_data()
# Newly added attendences inherit the type of the bulk group
data['type'] = self.type
if parl_ids := form.parliamentarian_id.data:
data.pop('parliamentarian_id', None)
collection = AttendenceCollection(request.session)
memberships = (
request.session.query(PASCommissionMembership)
.filter(
PASCommissionMembership.commission_id
== form.commission_id.data
)
.all()
)
eligible = {p.id for p in get_attendance_parliamentarians(request)}
commission_parliamentarians = [
membership.parliamentarian_id
for membership in memberships
if membership.parliamentarian_id in eligible
]
unselected_parliamentarians = [
pid for pid in commission_parliamentarians
if pid not in parl_ids
]
for parliamentarian in unselected_parliamentarians:
if attendence := collection.query().filter(
Attendence.parliamentarian_id == parliamentarian,
Attendence.bulk_edit_id == self.bulk_edit_id,
).first():
Change.add(request, 'delete', attendence)
collection.delete(attendence)
notify_attendence = None
for parliamentarian_id in parl_ids:
attendence = collection.query().filter(
Attendence.parliamentarian_id == parliamentarian_id,
Attendence.bulk_edit_id == self.bulk_edit_id,
).first()
if attendence:
was_abschluss = attendence.abschluss
form.populate_obj(attendence)
Change.add(request, 'edit', attendence)
if (
attendence.abschluss
and not was_abschluss
and notify_attendence is None
):
notify_attendence = attendence
else:
attendence = collection.add(
parliamentarian_id=parliamentarian_id, **data
)
attendence.bulk_edit_id = self.bulk_edit_id
Change.add(request, 'add', attendence)
if attendence.abschluss and notify_attendence is None:
notify_attendence = attendence
if notify_attendence is not None:
notify_admins_finalized(request, notify_attendence)
request.success(_('Edited attendences'))
return request.redirect(request.class_link(AttendenceCollection))
elif not request.POST:
form.process(obj=self)
layout = AttendenceCollectionLayout(self, request)
layout.breadcrumbs = [
Link(_('Homepage'), layout.homepage_url),
Link(_('Attendences'), request.class_link(AttendenceCollection)),
Link(_('Edit session'), '#')
]
layout.edit_mode = True
layout.editmode_links[1] = BackLink(
attrs={'class': 'cancel-link'},
)
if request.is_admin:
layout.editmode_links.append(
Link(
text=_('Delete'),
url=layout.csrf_protected_url(
request.link(self, name='delete-attendences')
),
traits=(
Confirm(
_('Do you really want to delete this bulk edit?'),
_('This cannot be undone.'),
_('Delete Attendencess'),
_('Cancel'),
),
Intercooler(
request_method='DELETE',
redirect_after=request.class_link(
AttendenceCollection
),
),
),
attrs={'class': 'delete-link'},
)
)
return {
'layout': layout,
'title': title,
'form': form,
'form_width': 'large'
}
@PasApp.form(
model=AttendenceCollection,
name='new-bulk',
template='form.pt',
form=AttendenceAddPlenaryForm
)
[docs]
def add_plenary_attendence(self: AttendenceCollection,
request: PasRequest,
form: AttendenceAddPlenaryForm
) -> RenderData | Response:
request.include('custom')
if not request.is_admin:
request.alert(_('You do not have permission to add plenary sessions.'))
return request.redirect(request.link(self))
if form.submitted(request):
# Check if attendance date is in a closed settlement run
if form.date.data:
if error := validate_attendance_date(
request.session, form.date.data
):
request.alert(error)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': _('New plenary session'),
'form': form,
'form_width': 'large'
}
data = form.get_useful_data()
# Remove static field; choices are set dynamically via JS
if parl_ids := data.pop('parliamentarian_id', None):
bulk_edit_id = uuid.uuid4()
for parliamentarian_id in parl_ids:
attendence = self.add(
parliamentarian_id=parliamentarian_id,
**data,
)
attendence.bulk_edit_id = bulk_edit_id
Change.add(request, 'add', attendence)
request.success(_('Added plenary session'))
return request.redirect(request.link(self))
layout = AttendenceCollectionLayout(self, request)
layout.breadcrumbs.append(Link(_('New plenary session'), '#'))
layout.edit_mode = True
return {
'layout': layout,
'title': _('New plenary session'),
'form': form,
'form_width': 'large'
}
@PasApp.html(
model=Attendence,
template='attendence.pt',
permission=Private
)
[docs]
def view_attendence(
self: Attendence,
request: PasRequest
) -> RenderData:
layout = AttendenceLayout(self, request)
return {
'layout': layout,
'attendence': self,
'title': layout.title,
}
@PasApp.form(
model=Attendence,
name='edit',
template='form.pt',
form=AttendenceForm,
permission=Private
)
[docs]
def edit_attendence(
self: Attendence,
request: PasRequest,
form: AttendenceForm
) -> RenderData | Response:
if not request.can_edit_attendence(self):
request.alert(_('Insufficient permissions.'))
return request.redirect(request.class_link(AttendenceCollection))
if self.bulk_edit_id:
name = (
'edit-plenary-bulk-attendences'
if self.type == 'plenary'
else 'edit-commission-bulk-attendences'
)
return request.redirect(request.link(self, name))
if form.submitted(request):
if not request.is_admin:
assert form.date.data is not None
if (
form.type.data == 'plenary'
or form.commission_id.data != self.commission_id
or form.parliamentarian_id.data != self.parliamentarian_id
or not request.can_edit_commission_attendance(
form.date.data,
self.commission_id,
self.parliamentarian_id,
)
):
request.alert(_('Insufficient permissions.'))
return request.redirect(
request.class_link(AttendenceCollection)
)
if form.date.data:
if error := validate_attendance_date(
request.session, form.date.data
):
request.alert(error)
return {
'layout': AttendenceCollectionLayout(self, request),
'title': _('New plenary session'),
'form': form,
'form_width': 'large'
}
was_abschluss = self.abschluss
form.populate_obj(self)
Change.add(request, 'edit', self)
if self.abschluss and not was_abschluss:
notify_admins_finalized(request, self)
request.success(_('Your changes were saved'))
return request.redirect(request.link(self))
form.process(obj=self)
layout = AttendenceLayout(self, request)
layout.breadcrumbs.append(Link(_('Edit'), '#'))
layout.edit_mode = True
return {
'layout': layout,
'title': layout.title,
'form': form,
'form_width': 'large'
}
@PasApp.view(
model=Attendence,
request_method='DELETE',
)
[docs]
def delete_attendence(
self: Attendence,
request: PasRequest
) -> None:
request.assert_valid_csrf_token()
if not request.is_admin:
request.alert(_('Only admins can delete attendance records.'))
return
if self.bulk_edit_id:
request.alert(_('Cannot delete individual bulk attendance.'))
return
# Check if attendance is in a closed settlement run
settlement_run = request.session.query(SettlementRun).filter(
SettlementRun.start <= self.date,
SettlementRun.end >= self.date,
SettlementRun.closed == True
).first()
if settlement_run:
request.alert(
_('Cannot delete attendance in closed settlement run.')
)
return
Change.add(request, 'delete', self)
collection = AttendenceCollection(request.session)
collection.delete(self)
@PasApp.view(
model=Attendence,
name='delete-attendences',
request_method='DELETE',
permission=Private
)
[docs]
def delete_attendences(
self: Attendence,
request: TownRequest
) -> None:
request.assert_valid_csrf_token()
if not request.is_admin:
request.alert(_('Only admins can delete attendance records.'))
return
attendences = request.session.query(Attendence).filter(
Attendence.bulk_edit_id == self.bulk_edit_id
).all()
# Check if any attendance is in a closed settlement run
for attendence in attendences:
settlement_run = request.session.query(SettlementRun).filter(
SettlementRun.start <= attendence.date,
SettlementRun.end >= attendence.date,
SettlementRun.closed == True
).first()
if settlement_run:
request.alert(
_('Cannot delete attendance in closed settlement run.')
)
return
for attendence in attendences:
Change.add(request, 'delete', attendence)
collection = AttendenceCollection(request.session)
collection.delete(attendence)
request.success(_('Deleted ${count} attendeces', mapping={
'count': len(attendences)
}))