Source code for town6.views.meeting

from __future__ import annotations

import morepath
import os
import zipfile

from pathlib import Path
from tempfile import NamedTemporaryFile

from webob.exc import HTTPNotFound
from webob.response import Response

from onegov.core.elements import Link
from onegov.core.html import sanitize_html
from onegov.core.security.permissions import Public, Private
from onegov.core.utils import normalize_for_path, normalize_for_filename
from onegov.org.forms import MeetingForm
from onegov.org.forms.meeting import MeetingExportPoliticalBusinessForm

from onegov.org.models import Meeting
from onegov.org.models import MeetingCollection
from onegov.org.models import MeetingItem
from onegov.org.models import PoliticalBusiness
from onegov.org.models.political_business import POLITICAL_BUSINESS_TYPE
from onegov.town6 import _
from onegov.town6 import TownApp
from onegov.town6.layout import MeetingCollectionLayout
from onegov.town6.layout import MeetingLayout

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from onegov.town6.request import TownRequest
    from onegov.core.types import RenderData
    from onegov.core.request import CoreRequest


[docs] def get_meeting_form_class( model: object, request: TownRequest ) -> type[MeetingForm]: if isinstance(model, Meeting): return model.with_content_extensions(MeetingForm, request) return Meeting(title='title').with_content_extensions( MeetingForm, request )
@TownApp.html( model=MeetingCollection, template='meetings.pt', permission=Public, )
[docs] def view_meetings( self: MeetingCollection, request: TownRequest, layout: MeetingCollectionLayout | None = None ) -> RenderData: if not request.app.org.ris_enabled: raise HTTPNotFound() filters = {} filters['past'] = [ Link( text=request.translate(title), active=self.past == value, url=request.link(self.for_filter(past=value)) ) for title, value in ( (_('Past Meetings'), True), (_('Upcoming Meetings'), False) ) ] upcoming_meeting = ( MeetingCollection(request.session, past=False) .query() .order_by(Meeting.past) .first() ) return { 'filters': filters, 'layout': layout or MeetingCollectionLayout(self, request), 'meetings': self.query().all(), 'upcoming_meeting': upcoming_meeting, 'past_title': self.past, 'title': _('Meetings'), }
@TownApp.form( model=MeetingCollection, name='new', template='form.pt', permission=Private, form=MeetingForm )
[docs] def add_meeting( self: MeetingCollection, request: TownRequest, form: MeetingForm ) -> RenderData | Response: if not request.app.org.ris_enabled: raise HTTPNotFound() layout = MeetingCollectionLayout(self, request) if form.submitted(request): meeting = self.add(**form.get_useful_data()) request.success(_('Added a new meeting')) return request.redirect(request.link(meeting)) layout.breadcrumbs.append(Link(_('New'), '#')) layout.edit_mode = True return { 'layout': layout, 'form': form, 'title': _('New meeting'), 'form_width': 'large' }
@TownApp.html( model=Meeting, template='meeting.pt', permission=Public, )
[docs] def view_meeting( self: Meeting, request: TownRequest, ) -> RenderData: if not request.app.org.ris_enabled: raise HTTPNotFound() layout = MeetingLayout(self, request) title = ( self.title + ' - ' + layout.format_date(self.start_datetime, 'date') if self.start_datetime else self.title ) # Construct meeting items with political business links meeting_items_with_links = [] for item in self.meeting_items or []: item_data = { 'number': item.number, 'title': item.title, 'business_type': None, 'political_business_link': None } if item.political_business_link_id: business = request.session.query(PoliticalBusiness).filter( PoliticalBusiness.meta['self_id'].astext == item.political_business_link_id ).first() if business is not None: item_data['political_business_link'] = request.link(business) item_data['business_type'] = ( POLITICAL_BUSINESS_TYPE)[business.political_business_type] else: if item.political_business: item_data['political_business_link'] = ( request.link(item.political_business)) item_data['business_type'] = ( POLITICAL_BUSINESS_TYPE)[item.political_business.political_business_type] meeting_items_with_links.append(item_data) meeting_items_with_links.sort( key=lambda x: (x['number'] or '', x['title'] or '') ) links: list[tuple[str, str]] = [] if self.audio_link.strip(): links.append((_('Listen to parliamentary debate'), self.audio_link)) default = ('https://live.stadtwil.ch/' if request.app.org.name == 'Stadt Wil' else '') video_link = self.video_link or default if video_link.strip(): links.append((_('Watch parliamentary debate'), video_link)) return { 'layout': layout, 'page': self, 'text': '', 'lead': '', 'people': getattr(self, 'people', None), 'files': getattr(self, 'files', None), 'contact': getattr(self, 'contact_html', None), 'coordinates': None, 'title': title, 'meeting_items_with_links': meeting_items_with_links, 'show_side_panel': True if links else False, 'sidepanel_links': links, }
@TownApp.form( model=Meeting, name='edit', template='form.pt', permission=Private, form=get_meeting_form_class, pass_model=True, )
[docs] def edit_meeting( self: Meeting, request: TownRequest, form: MeetingForm ) -> RenderData | Response: if not request.app.org.ris_enabled: raise HTTPNotFound() layout = MeetingLayout(self, request) if form.submitted(request): form.populate_obj(self) request.success(_('Your changes were saved')) return request.redirect(request.link(self)) layout.breadcrumbs.append(Link(_('Edit'), '#')) layout.include_editor() layout.editbar_links = [] layout.edit_mode = True return { 'layout': layout, 'form': form, 'title': _('Edit meeting'), 'form_width': 'full' }
@TownApp.view( model=Meeting, request_method='DELETE', permission=Private )
[docs] def delete_meeting( self: Meeting, request: TownRequest ) -> None: if not request.app.org.ris_enabled: raise HTTPNotFound() request.assert_valid_csrf_token() collection = MeetingCollection(request.session) collection.delete(self) request.success(_('The meeting has been deleted.'))
@TownApp.view(model=MeetingItem, permission=Public)
[docs] def view_redirect_meeting_item_to_meeting( self: MeetingItem, request: CoreRequest ) -> Response: """ Redirect for search results, if we link to MeetingItem we show the Meeting """ return morepath.redirect(request.link(self.meeting))
@TownApp.form( model=Meeting, permission=Public, name='export', template='export.pt', form=MeetingExportPoliticalBusinessForm, pass_model=True )
[docs] def view_meeting_export( self: Meeting, request: TownRequest, form: MeetingExportPoliticalBusinessForm ) -> RenderData | Response: def build_zip_response() -> Response: meeting_doc_ids = form.get_selected_meeting_documents_ids() agenda_item_doc_ids = form.get_selected_agenda_item_document_ids() base_storage_path = request.app.depot_storage_path assert (base_storage_path is not None), ( 'Depot storage path is not configured') with (NamedTemporaryFile(delete=False) as f): zip_path = f.name with zipfile.ZipFile( zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file: # meeting documents for file in self.files: if file.id in meeting_doc_ids: path = ( Path(base_storage_path) / file.reference.path / 'file' ) with open(path, 'rb') as file_content: folder_name = normalize_for_path(self.display_name) zip_file.writestr( os.path.join(folder_name, file.name), file_content.read() ) # agenda item documents for meeting_item in self.meeting_items: business = meeting_item.political_business if business: for file in business.files: if file.id in agenda_item_doc_ids: path = ( Path(base_storage_path) / file.reference.path / 'file') with open(path, 'rb') as file_content: folder_name = normalize_for_path( meeting_item.display_name) zip_file.writestr( os.path.join(folder_name, file.name), file_content.read() ) with open(zip_path, 'rb') as zip_file: filename = normalize_for_filename(self.display_name) response = Response() response.body = zip_file.read() response.content_type = 'application/zip' response.content_disposition = ( f'attachment; filename="{filename}.zip"') return response layout = MeetingLayout(self, request) layout.breadcrumbs.append(Link(_('Export'), '#')) layout.editbar_links = None file_count = 0 file_count += len(self.files) for meeting_item in self.meeting_items: if meeting_item.political_business: file_count += len(meeting_item.political_business.files) if form.submitted(request): form.populate_obj(self) return build_zip_response() meeting_items_no_docs = [] if not self.files: meeting_items_no_docs.append(self.display_name) for meeting_item in self.meeting_items: if not meeting_item.political_business: meeting_items_no_docs.append(meeting_item.display_name) else: if not meeting_item.political_business.files: meeting_items_no_docs.append(meeting_item.display_name) meeting_items_no_docs = sorted(meeting_items_no_docs) intro = request.translate( _('No documents are assigned to the following agenda items:') ) items = ''.join(f'<li>{title}</li>' for title in meeting_items_no_docs) note_html = intro + f'<ul>{items}</ul>' return { 'layout': layout, 'form': form, 'title': _('Export meeting documents'), 'explanation': _('Select the meeting and agenda item ' 'documents you want to export. The resulting zipfile ' 'contains the documents per meeting item.'), 'has_note': True if meeting_items_no_docs else False, 'note_html': sanitize_html(note_html), 'filters': None, 'count': file_count, }