def boxes(self): items = [ [{'id': 'upcoming_meetings', 'label': _('label_upcoming_meetings', default=u'Upcoming meetings'), 'content': self.upcoming_meetings(), 'href': 'meetings'}, {'id': 'closed_meetings', 'label': _('label_closed_meetings', default=u'Closed meetings'), 'content': self.closed_meetings(), 'href': 'meetings'}], [{'id': 'unscheduled_proposals', 'label': _('label_unscheduled_proposals', default=u'Unscheduled proposals'), 'content': self.unscheduled_proposals(), 'href': 'submittedproposals'}], [{'id': 'period', 'label': _('label_current_period', default=u'Current Period'), 'content': [self.period()], 'href': ''}, {'id': 'current_members', 'label': _('label_current_members', default=u'Current members'), 'content': self.current_members(), 'href': 'memberships'}], ] return items
def get_participants(self): result = [] participants = self.model.participants presidency = self.model.presidency secretary = self.model.secretary for membership in Membership.query.for_meeting(self.model): item = {'fullname': membership.member.fullname, 'email': membership.member.email, 'member_id': membership.member.member_id} if membership.member in participants: item['presence_cssclass'] = 'presence present' else: item['presence_cssclass'] = 'presence not-present' if membership.member == presidency: item['role'] = {'name': 'presidency', 'label': _(u'meeting_role_presidency', default=u'Presidency')} elif membership.member == secretary: item['role'] = {'name': 'secretary', 'label': _(u'meeting_role_secretary', default=u'Secretary')} else: item['role'] = {'name': '', 'label': ''} result.append(item) result.sort(key=itemgetter('fullname')) return result
def __call__(self): # Enable border to show the zip export action also for # committee members. Because the plone_view's `showEditableBorder` # checks for `ModifyPortalContent`, we have to enable the border # manually. self.request.set('enable_border', True) # Check that user permissions on the meeting dossier match his permissions # on the meeting_dossier, as otherwise several elements in the meeting view # will break. meeting_dossier = self.model.get_dossier() committee = self.model.committee.resolve_committee() if (api.user.has_permission('Modify portal content', obj=committee) and not api.user.has_permission('Modify portal content', obj=meeting_dossier) and not self.model.is_closed()): self.error_message = translate( _("no_edit_permissions_on_meeting_dossier", default="User does not have permission to edit the meeting dossier:"), context=self.request) self.meeting_dossier_link = linked(meeting_dossier, meeting_dossier.Title()) return self.error_template() elif (api.user.has_permission('View', obj=committee) and not api.user.has_permission('View', obj=meeting_dossier)): self.error_message = translate( _("no_view_permissions_on_meeting_dossier", default="User does not have permission to view the meeting dossier:"), context=self.request) self.meeting_dossier_link = linked(meeting_dossier, meeting_dossier.Title(), with_tooltip=False) return self.error_template() return self.template()
def edit(self): """Updates the title of the agendaitem, with the one given by the request parameter `title`. """ self.require_agendalist_editable() title = safe_unicode(self.request.get('title')) description = safe_unicode(self.request.get('description')) if not title: return JSONResponse(self.request).error( _('agenda_item_update_empty_string', default=u"Agenda Item title must not be empty.")).proceed().dump() if self.agenda_item.has_proposal: if len(title) > ISubmittedProposal['title'].max_length: return JSONResponse(self.request).error( _('agenda_item_update_too_long_title', default=u"Agenda Item title is too long.") ).proceed().dump() self.agenda_item.set_title(title) self.agenda_item.set_description(description) return JSONResponse(self.request).info( _('agenda_item_updated', default=u"Agenda Item updated.")).proceed().dump()
def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except (WrongAgendaItemState, CannotExecuteTransition): return JSONResponse(getRequest()).error( _(u'invalid_agenda_item_state', default=u'The agenda item is in an invalid state for ' 'this action.'), status=403).dump() except Forbidden: return JSONResponse(getRequest()).error( _(u'editing_not_allowed', default=u'Editing is not allowed.'), status=403).dump() except MissingMeetingDossierPermissions: return JSONResponse(getRequest()).error( _('error_no_permission_to_add_document', default=u'Insufficient privileges to add a ' u'document to the meeting dossier.'), status=403).dump() except MissingAdHocTemplate: return JSONResponse(getRequest()).error( _('missing_ad_hoc_template', default=u"No ad-hoc agenda-item template has been " u"configured."), status=501).dump() except SablonProcessingFailed: return JSONResponse(getRequest()).error( _('Error while processing Sablon template'), status=500).dump()
def deactivate(self): model = self.context.load_model() if model.has_pending_meetings(): api.portal.show_message( message=_('msg_pending_meetings', default=u'Not all meetings are closed.'), request=self.request, type='error') return self.redirect() if model.has_unscheduled_proposals(): api.portal.show_message( message=_('msg_unscheduled_proposals', default=u'There are unscheduled proposals submitted' ' to this committee.'), request=self.request, type='error') return self.redirect() self.wf_tool.doActionFor(self.context, DEACTIVATE) model.deactivate() api.portal.show_message( message=_(u'label_committe_deactivated', default="Committee deactivated successfully"), request=self.request, type='info')
def decide(self): """Decide the current agendaitem and move the meeting in the held state. """ meeting_state = self.meeting.get_state() if not self.context.model.is_editable(): raise Unauthorized("Editing is not allowed") self.agenda_item.decide() response = JSONResponse(self.request) if self.agenda_item.has_proposal: response.info( _(u'agenda_item_proposal_decided', default=u'Agenda Item decided and excerpt generated.')) else: response.info(_(u'agenda_item_decided', default=u'Agenda Item decided.')) if meeting_state != self.meeting.get_state(): response.redirect(self.context.absolute_url()) msg = _(u'agenda_item_meeting_held', default=u"Agendaitem has been decided and the meeting has been held.") api.portal.show_message(message=msg, request=self.request, type='info') return response.dump()
def edit(self): """Updates the title of the agendaitem, with the one given by the request parameter `title`. """ if not self.context.model.is_editable(): raise Unauthorized("Editing is not allowed") title = self.request.get('title') if not title: return JSONResponse(self.request).error( _('agenda_item_update_empty_string', default=u"Agenda Item title must not be empty.")).proceed().dump() title = title.decode('utf-8') if self.agenda_item.has_proposal: if len(title) > ISubmittedProposalModel['title'].max_length: return JSONResponse(self.request).error( _('agenda_item_update_too_long_title', default=u"Agenda Item title is too long.") ).proceed().dump() self.agenda_item.set_title(title) return JSONResponse(self.request).info( _('agenda_item_updated', default=u"Agenda Item updated.")).proceed().dump()
def get_overview_attributes(self): data = super(SubmittedProposal, self).get_overview_attributes() model = self.load_model() # Insert dossier link if dossier exists after committee data.insert( 2, { 'label': _('label_dossier', default=u"Dossier"), 'value': self.get_dossier_link(), 'is_html': True, } ) # Insert considerations after proposed_action data.insert( 7, { 'label': _('label_considerations', default=u"Considerations"), 'value': model.considerations, } ) # Insert discussion after considerations agenda = model.agenda_item data.insert( 8, { 'label': _('label_discussion', default=u"Discussion"), 'value': agenda and agenda.discussion or '' } ) return data
def __call__(self): response = self.request.response template = self.model.get_toc_template() if not template: api.portal.show_message( _('msg_no_toc_template', default=u'There is no toc template configured, toc could ' 'not be generated.'), request=self.request, type='error') response.setHeader('X-ogg-reload-page', "True") return sablon = Sablon(template) try: sablon.process(self.get_json_data()) except SablonProcessingFailed: message = _(u'Error while processing Sablon template') api.portal.show_message(message, request=self.request, type='error') response.setHeader('X-ogg-reload-page', "True") return filename = self.get_filename().encode('utf-8') response.setHeader('X-Theme-Disabled', 'True') response.setHeader('Content-Type', MIME_DOCX) response.setHeader("Content-Disposition", 'attachment; filename="{}"'.format(filename)) return sablon.file_data
def __call__(self): meeting = self.get_meeting() command = CreateGeneratedDocumentCommand( self.context, meeting, self.operations, ) try: command.execute() command.show_message() except AgendaItemListMissingTemplate: msg = _( u'msg_error_agendaitem_list_missing_template', default=(u'There is no agendaitem list template configured, agendaitem list could not be generated.'), mapping=dict(title=meeting.get_title()), ) api.portal.show_message(msg, self.request, type='error') except AgendaItemListAlreadyGenerated: msg = _( u'msg_error_agendaitem_list_already_generated', default=(u'The agenda item list for meeting ${title} has already been generated.'), mapping=dict(title=meeting.get_title()), ) api.portal.show_message(msg, self.request, type='error') except SablonProcessingFailed: msg = _(u'Error while processing Sablon template') api.portal.show_message(msg, self.request, type='error') return self.request.RESPONSE.redirect(meeting.get_url())
def _get_tabs(self): return [ {"id": "overview", "title": _(u"overview", default=u"Overview")}, {"id": "meetings", "title": _(u"meetings", default=u"Meetings")}, {"id": "submittedproposals", "title": _(u"submittedproposals", default=u"Submitted Proposals")}, {"id": "memberships", "title": _(u"memberships", default=u"Memberships")}, {"id": "periods", "title": _(u"periods", default=u"Periods")}, ]
def schedule_text(self): """Schedule the given Text (request parameter `title`) for the current meeting. """ self.check_editable() title = self.request.get('title') if not title: return JSONResponse(self.request).error( _('empty_proposal', default=u"Proposal must not be empty.")).proceed().dump() self.meeting.schedule_text(title) return JSONResponse(self.request).info( _('text_added', default=u"Text successfully added.")).proceed().dump()
def get_agenda_item_attachment_filename(self, document, agenda_item_number, attachment_number): return normalize_path(u'{}/{}/{}_{}'.format( translate( _(u'title_agenda_item', default=u'Agenda item ${agenda_item_number}', mapping={u'number': agenda_item_number}), context=getRequest(), ), translate( _(u'attachments', default=u'Attachments'), context=getRequest(), ), str(attachment_number), safe_unicode(self.get_filename(document))) )
def handleApply(self, action): """""" if self.has_write_conflict(): self.status = _(u'message_write_conflict', default='Your changes were not saved, the protocol has ' 'been modified in the meantime.') return if self.is_locked_by_another_user(): self.status = _(u'message_locked_by_another_user', default='Your changes were not saved, the protocol is ' 'locked by ${username}.', mapping={'username': self.get_lock_creator_user_name()}) return super(EditMeetingView, self).handleApply(self, action)
def schedule_paragraph(self): """Schedule the given Paragraph (request parameter `title`) for the current meeting. """ self.check_editable() title = self.request.get('title') if not title: return JSONResponse(self.request).error( _('empty_paragraph', default=u"Paragraph must not be empty.")).proceed().dump() self.meeting.schedule_text(title, is_paragraph=True) return JSONResponse(self.request).info( _('paragraph_added', default=u"Paragraph successfully added.") ).proceed().dump()
def columns(self): return ( {'column': 'decision_number', 'column_title': _(u'label_decision_number', default=u'Decision number'), 'transform': lambda item, value: item.get_decision_number(), 'sortable': False, 'groupable': False, 'width': 80}, {'column': 'title', 'column_title': _(u'column_title', default=u'Title'), 'transform': proposal_link, 'sortable': True, 'groupable': False, 'width': 180}, {'column': 'description', 'column_title': _(u'column_description', default=u'Description'), 'transform': get_description, 'sortable': True, 'groupable': False, 'width': 180}, {'column': 'workflow_state', 'column_title': _(u'column_state', default=u'State'), 'transform': translated_state, 'width': 120}, {'column': 'committee_id', 'column_title': _(u'column_comittee', default=u'Comittee'), 'transform': lambda item, value: item.committee.get_link(), 'width': 180}, {'column': 'generated_meeting_link', 'column_title': _(u'column_meeting', default=u'Meeting'), 'transform': lambda item, value: item.get_meeting_link(), 'sortable': False, 'groupable': False, 'width': 180}, {'column': 'date_of_submission', 'column_title': _(u'column_date_of_submission', default=u'Date of submission'), 'transform': helper.readable_date, 'sortable': True, 'groupable': True, 'width': 120}, {'column': 'issuer', 'column_title': _(u'label_issuer', default=u'Issuer'), 'transform': linked_ogds_author, 'sortable': True, 'groupable': True, 'width': 200}, )
def render(self): if not self.context.is_submitted_document(): raise NoSubmittedDocument() if self.context.is_checked_out(): raise Unauthorized() with elevated_privileges(): transporter = Transporter() transporter.update(self.context, self.request) portal_path = '/'.join(api.portal.get().getPhysicalPath()) intids = getUtility(IIntIds) repository = api.portal.get_tool('portal_repository') comment = translate( _(u"Updated with a newer docment version from proposal's " "dossier."), context=self.request) repository.save(obj=self.context, comment=comment) data = { 'path': '/'.join(self.context.getPhysicalPath())[ len(portal_path) + 1:], 'intid': intids.queryId(self.context) } # Set correct content type for JSON response self.request.response.setHeader("Content-type", "application/json") return json.dumps(data)
def execute(self, obj, model, text=None, **kwargs): super(Cancel, self).execute(obj, model) model.cancel(text=text) msg = _(u'msg_proposal_cancelled', default=u'Proposal cancelled successfully.') api.portal.show_message(msg, request=getRequest(), type='info')
def execute(self, obj, model, text=None, **kwargs): super(Reactivate, self).execute(obj, model) model.reactivate(text) msg = _(u'msg_proposal_reactivated', default=u'Proposal reactivated successfully.') api.portal.show_message(msg, request=getRequest(), type='info')
def columns(self): return ( {'column': 'firstname', 'column_title': _(u'column_firstname', default=u'Firstname'), 'transform': self.get_firstname_link, }, {'column': 'lastname', 'column_title': _(u'column_lastname', default=u'Lastname'), 'transform': self.get_lastname_link, }, {'column': 'email', 'column_title': _(u'column_email', default=u'E-Mail'), }, )
def show_message(self): portal = api.portal.get() api.portal.show_message( _(u'Document ${title} has already been submitted in that version.', mapping=dict(title=self.document.title)), portal.REQUEST, type='warning')
def get_validation_errors(self, model): if model.get_undecided_agenda_items(): return [_(u'label_close_error_has_undecided_agenda_items', u'The meeting cannot be closed because it has undecided' u' agenda items.')] return ()
def handleApply(self, action): data, errors = self.extractData() if errors: return agenda_items_to_include = [] for agenda_item in self.get_agenda_items(): if agenda_item.name in self.request: agenda_items_to_include.append(agenda_item) if not agenda_items_to_include: raise(ActionExecutionError( Invalid(_(u"Please select at least one agenda item.")))) operations = ManualExcerptOperations( agenda_items_to_include, data['title'], include_initial_position=data['include_initial_position'], include_legal_basis=data['include_legal_basis'], include_considerations=data['include_considerations'], include_proposed_action=data['include_proposed_action'], include_discussion=data['include_discussion'], include_decision=data['include_decision'], include_publish_in=data['include_publish_in'], include_disclose_to=data['include_disclose_to'], include_copy_for_attention=data['include_copy_for_attention']) command = CreateGeneratedDocumentCommand( data['dossier'], self.model, operations) command.execute() command.show_message() return self.redirect_to_meeting()
def schedule_ad_hoc(self, title): committee = self.committee.resolve_committee() ad_hoc_template = committee.get_ad_hoc_template() if not ad_hoc_template: raise MissingAdHocTemplate meeting_dossier = self.get_dossier() if not api.user.get_current().checkPermission( 'opengever.document: Add document', meeting_dossier): raise MissingMeetingDossierPermissions document_title = _(u'title_ad_hoc_document', default=u'Ad hoc agenda item ${title}', mapping={u'title': title}) ad_hoc_document = CreateDocumentCommand( context=meeting_dossier, filename=ad_hoc_template.file.filename, data=ad_hoc_template.file.data, content_type=ad_hoc_template.file.contentType, title=translate(document_title, context=getRequest())).execute() agenda_item = AgendaItem( title=title, document=ad_hoc_document, is_paragraph=False) self.agenda_items.append(agenda_item) self.reorder_agenda_items() return agenda_item
def build_linked_meeting_box(self): box = { 'id': 'linked_meeting', 'content': self.linked_meeting(), 'label': _('label_linked_meeting', default='Linked meeting') } return box
def __call__(self): template = self.model.get_toc_template() if not template: api.portal.show_message( _('msg_no_toc_template', default=u'There is no toc template configured, toc could ' 'not be generated.'), request=self.request, type='error') return self.request.RESPONSE.redirect( "{}/#periods".format(self.context.parent.absolute_url())) sablon = Sablon(template) sablon.process(self.get_json_data()) assert sablon.is_processed_successfully(), sablon.stderr filename = self.get_filename().encode('utf-8') response = self.request.response response.setHeader('X-Theme-Disabled', 'True') response.setHeader('Content-Type', MIME_DOCX) response.setHeader("Content-Disposition", 'attachment; filename="{}"'.format(filename)) return sablon.file_data
def get_overview_attributes(self): model = self.load_model() assert model, 'missing db-model for {}'.format(self) attributes = [ {'label': _(u"label_title", default=u'Title'), 'value': self.title}, {'label': _('label_committee', default=u'Committee'), 'value': model.committee.get_link(), 'is_html': True}, {'label': _('label_meeting', default=u'Meeting'), 'value': model.get_meeting_link(), 'is_html': True}, ] proposal_document = self.get_proposal_document() if proposal_document: attributes.append({ 'label': _('proposal_document', default=u'Proposal document'), 'value': DocumentLinkWidget(proposal_document).render(), 'is_html': True}) attributes.extend([ {'label': _('label_workflow_state', default=u'State'), 'value': self.get_state().title, 'is_html': True}, {'label': _('label_decision_number', default=u'Decision number'), 'value': model.get_decision_number(), 'is_html': True}, ]) if self.predecessor_proposal and self.predecessor_proposal.to_object: predecessor_model = self.predecessor_proposal.to_object.load_model() attributes.append({ 'label': _('label_predecessor', default=u'Predecessor'), 'value': predecessor_model.get_link(), 'is_html': True}) catalog = getUtility(ICatalog) doc_id = getUtility(IIntIds).getId(aq_inner(self)) successor_html_items = [] for relation in catalog.findRelations({ 'to_id': doc_id, 'from_attribute': 'predecessor_proposal'}): successor_html_items.append(u'<li>{}</li>'.format( relation.from_object.load_model().get_link())) if successor_html_items: attributes.append({ 'label': _('label_successors', default=u'Successors'), 'value': u'<ul>{}</ul>'.format(''.join(successor_html_items)), 'is_html': True}) return attributes
def get_group_title(self, group_key, contents): if group_key: return repo_refnum(contents[0]) ad_hoc_title = translate(_(u'ad_hoc_toc_group_title', default=u'Ad hoc agendaitems'), context=getRequest()) return ad_hoc_title
def get_group_title(group_key, contents): if group_key: return group_key ad_hoc_title = translate(_(u'ad_hoc_toc_group_title', default=u'Ad hoc agendaitems'), context=getRequest()) return ad_hoc_title
class Meeting(Base, SQLFormSupport): STATE_PENDING = State('pending', is_default=True, title=_('pending', default='Pending')) STATE_HELD = State('held', title=_('held', default='Held')) STATE_CLOSED = State('closed', title=_('closed', default='Closed')) STATE_CANCELLED = State('cancelled', title=_('cancelled', default='Cancelled')) workflow = Workflow( [STATE_PENDING, STATE_HELD, STATE_CLOSED, STATE_CANCELLED], [ CloseTransition('pending', 'closed', title=_('close_meeting', default='Close meeting')), Transition('pending', 'held', title=_('hold', default='Hold meeting'), visible=False), CloseTransition('held', 'closed', title=_('close_meeting', default='Close meeting')), Transition('closed', 'held', title=_('reopen', default='Reopen')), CancelTransition( 'pending', 'cancelled', title=_('cancel', default='Cancel')), ], show_in_actions_menu=True, transition_controller=MeetingTransitionController, ) __tablename__ = 'meetings' meeting_id = Column("id", Integer, Sequence("meeting_id_seq"), primary_key=True) committee_id = Column(Integer, ForeignKey('committees.id'), nullable=False) committee = relationship("Committee", backref='meetings') location = Column(String(256)) title = Column(UnicodeCoercingText) start = Column('start_datetime', UTCDateTime(timezone=True), nullable=False) end = Column('end_datetime', UTCDateTime(timezone=True)) workflow_state = Column(String(WORKFLOW_STATE_LENGTH), nullable=False, default=workflow.default_state.name) modified = Column(UTCDateTime(timezone=True), nullable=False, default=utcnow_tz_aware) meeting_number = Column(Integer) presidency = relationship( 'Member', primaryjoin="Member.member_id==Meeting.presidency_id") presidency_id = Column(Integer, ForeignKey('members.id')) secretary_id = Column(String(USER_ID_LENGTH), ForeignKey(User.userid)) secretary = relationship(User, primaryjoin=User.userid == secretary_id) other_participants = Column(UnicodeCoercingText) participants = relationship('Member', secondary=meeting_participants, order_by='Member.lastname, Member.firstname', backref='meetings') dossier_admin_unit_id = Column(String(UNIT_ID_LENGTH), nullable=False) dossier_int_id = Column(Integer, nullable=False) dossier_oguid = composite(Oguid, dossier_admin_unit_id, dossier_int_id) agenda_items = relationship("AgendaItem", order_by='AgendaItem.sort_order', backref='meeting') protocol_document_id = Column(Integer, ForeignKey('generateddocuments.id')) protocol_document = relationship( 'GeneratedProtocol', uselist=False, backref=backref('meeting', uselist=False), primaryjoin= "GeneratedProtocol.document_id==Meeting.protocol_document_id") protocol_start_page_number = Column(Integer) agendaitem_list_document_id = Column(Integer, ForeignKey('generateddocuments.id')) agendaitem_list_document = relationship( 'GeneratedAgendaItemList', uselist=False, backref=backref('meeting', uselist=False), primaryjoin= "GeneratedAgendaItemList.document_id==Meeting.agendaitem_list_document_id" ) def was_protocol_manually_edited(self): """checks whether the protocol has been manually edited or not""" if not self.has_protocol_document(): return False document = self.protocol_document.resolve_document() return not self.protocol_document.is_up_to_date(document) def get_other_participants_list(self): if self.other_participants is not None: return filter( len, map(lambda value: value.strip(), self.other_participants.split('\n'))) else: return [] def initialize_participants(self): """Set all active members of our committee as participants of this meeting. """ self.participants = [ membership.member for membership in Membership.query.for_meeting(self) ] @property def absentees(self): return [ membership.member for membership in Membership.query.for_meeting(self) if membership.member not in set(self.participants) ] def __repr__(self): return '<Meeting at "{}">'.format(self.start) def generate_meeting_number(self): """Generate meeting number for self. This method locks the current period of this meeting to protect its meeting_sequence_number against concurrent updates. """ period = Period.query.get_current_for_update(self.committee) self.meeting_number = period.get_next_meeting_sequence_number() def get_meeting_number(self): # Before the meeting is held, it will not have a meeting number. # In that case we do not want to format it with the period title prefixed if not self.meeting_number: return None period = Period.query.get_for_meeting(self) if not period: return str(self.meeting_number) title = period.title return '{} / {}'.format(title, self.meeting_number) def generate_decision_numbers(self): """Generate decision numbers for each agenda item of this meeting. This method locks the current period of this meeting to protect its decision_sequence_number against concurrent updates. """ period = Period.query.get_current_for_update(self.committee) for agenda_item in self.agenda_items: agenda_item.generate_decision_number(period) def update_protocol_document(self, overwrite=False): """Update or create meeting's protocol.""" from opengever.meeting.command import MergeDocxProtocolCommand from opengever.meeting.command import ProtocolOperations operations = ProtocolOperations() command = MergeDocxProtocolCommand(self.get_dossier(), self, operations) command.execute(overwrite=overwrite) return command def hold(self): if self.workflow_state == 'held': return self.generate_meeting_number() self.generate_decision_numbers() self.workflow_state = 'held' def close(self): """Closes a meeting means set the meeting in the closed state. - generate and set the meeting number - generate decision numbers for each agenda_item - close each agenda item (generates proposal excerpt and change workflow state) - generate or update the protocol if necessary """ self.hold() assert not self.get_undecided_agenda_items(), \ 'All agenda items must be decided before a meeting is closed.' try: self.update_protocol_document() except SablonProcessingFailed: msg = _(u'Error while processing Sablon template') api.portal.show_message(msg, api.portal.get().REQUEST, type='error') return False self.workflow_state = 'closed' return True @property def css_class(self): return 'contenttype-opengever-meeting-meeting' def is_editable(self): committee = self.committee.resolve_committee() if not api.user.has_permission('Modify portal content', obj=committee): return False return self.is_active() def is_agendalist_editable(self): if not self.is_editable(): return False return self.is_pending() def is_pending(self): return self.get_state() == self.STATE_PENDING def is_active(self): return self.get_state() in [self.STATE_HELD, self.STATE_PENDING] def is_closed(self): return self.get_state() == self.STATE_CLOSED def has_protocol_document(self): return self.protocol_document is not None def has_agendaitem_list_document(self): return self.agendaitem_list_document is not None @property def wrapper_id(self): return 'meeting-{}'.format(self.meeting_id) def _get_title(self, prefix): return u"{}-{}".format(translate(prefix, context=getRequest()), self.get_title()) def _get_filename(self, prefix): normalizer = getUtility(IFileNameNormalizer, name='gever_filename_normalizer') return u"{}-{}.docx".format(translate(prefix, context=getRequest()), normalizer.normalize(self.get_title())) def get_protocol_title(self): return self._get_title(_("Protocol")) def get_excerpt_title(self): return self._get_title(_("Protocol Excerpt")) def get_agendaitem_list_title(self): return self._get_title( _(u'label_agendaitem_list', default=u'Agendaitem list')) def get_protocol_filename(self): return self._get_filename(_("Protocol")) def get_excerpt_filename(self): return self._get_filename(_("Protocol Excerpt")) def get_agendaitem_list_filename(self): return self._get_filename( _(u'label_agendaitem_list', default=u'Agendaitem list')) def get_protocol_header_template(self): return self.committee.get_protocol_header_template() def get_protocol_suffix_template(self): return self.committee.get_protocol_suffix_template() def get_agenda_item_header_template(self): return self.committee.get_agenda_item_header_template() def get_agenda_item_suffix_template(self): return self.committee.get_agenda_item_suffix_template() def get_agendaitem_list_template(self): return self.committee.get_agendaitem_list_template() @property def physical_path(self): return '/'.join((self.committee.physical_path, self.wrapper_id)) def execute_transition(self, name): self.workflow.execute_transition(self, self, name) def can_execute_transition(self, name): return self.workflow.can_execute_transition(self, name) def get_state(self): return self.workflow.get_state(self.workflow_state) def update_model(self, data): """Manually set the modified timestamp when updating meetings.""" super(Meeting, self).update_model(data) self.modified = utcnow_tz_aware() meeting_dossier = self.get_dossier() title = data.get('title') if meeting_dossier and title: meeting_dossier.title = title meeting_dossier.reindexObject() def get_title(self): return self.title def get_date(self): return api.portal.get_localized_time(datetime=self.start) def get_start(self): """Returns the start datetime in localized format. """ return api.portal.get_localized_time(datetime=self.start, long_format=True) def get_end(self): """Returns the end datetime in localized format. """ if self.end: return api.portal.get_localized_time(datetime=self.end, long_format=True) return None def get_start_time(self): return self._get_localized_time(self.start) def get_end_time(self): if not self.end: return None return self._get_localized_time(self.end) def get_undecided_agenda_items(self): """Return a filtered list of this meetings agenda items, containing only the items which are not in a "decided" workflow state. """ def is_not_paragraph(agenda_item): return not agenda_item.is_paragraph def is_not_decided(agenda_item): return not agenda_item.is_completed() return filter(is_not_decided, filter(is_not_paragraph, self.agenda_items)) def _get_localized_time(self, date): if not date: return None return api.portal.get_localized_time(datetime=date, time_only=True) def schedule_proposal(self, proposal): assert proposal.committee == self.committee proposal.schedule(self) def schedule_text(self, title, is_paragraph=False, description=None): self.agenda_items.append( AgendaItem(title=title, description=description, is_paragraph=is_paragraph)) self.reorder_agenda_items() def schedule_ad_hoc(self, title, template_id=None, description=None): committee = self.committee.resolve_committee() if template_id is None: ad_hoc_template = committee.get_ad_hoc_template() else: from opengever.meeting.vocabulary import ProposalTemplatesForCommitteeVocabulary vocabulary_factory = ProposalTemplatesForCommitteeVocabulary() vocabulary = vocabulary_factory(committee) templates = [ term.value for term in vocabulary if term.value.getId() == template_id ] assert 1 == len(templates) ad_hoc_template = templates[0] if not ad_hoc_template: raise MissingAdHocTemplate meeting_dossier = self.get_dossier() if not api.user.get_current().checkPermission( 'opengever.document: Add document', meeting_dossier): raise MissingMeetingDossierPermissions ad_hoc_document = CreateDocumentCommand( context=meeting_dossier, filename=ad_hoc_template.file.filename, data=ad_hoc_template.file.data, content_type=ad_hoc_template.file.contentType, title=title).execute() agenda_item = AgendaItem(title=title, description=description, document=ad_hoc_document, is_paragraph=False) self.agenda_items.append(agenda_item) self.reorder_agenda_items() return agenda_item def _set_agenda_item_order(self, new_order): agenda_items_by_id = OrderedDict( (item.agenda_item_id, item) for item in self.agenda_items) agenda_items = [] for agenda_item_id in new_order: agenda_item = agenda_items_by_id.pop(agenda_item_id, None) if agenda_item: agenda_items.append(agenda_item) agenda_items.extend(agenda_items_by_id.values()) self.agenda_items = agenda_items def reorder_agenda_items(self, new_order=None): if new_order: self._set_agenda_item_order(new_order) sort_order = 1 number = 1 for agenda_item in self.agenda_items: agenda_item.sort_order = sort_order sort_order += 1 if not agenda_item.is_paragraph: agenda_item.item_number = number number += 1 def get_submitted_link(self): return self._get_link(self.get_submitted_admin_unit(), self.submitted_physical_path) def get_link(self): url = self.get_url() if api.user.has_permission('View', obj=self.committee.resolve_committee()): link = u'<a href="{0}" title="{1}" class="{2}">{1}</a>'.format( url, escape_html(self.get_title()), self.css_class) else: link = u'<span title="{0}" class="{1}">{0}</a>'.format( escape_html(self.get_title()), self.css_class) return link def get_url(self, context=None, view='view'): elements = [ self.committee.get_admin_unit().public_url, self.physical_path ] if view: elements.append(view) return '/'.join(elements) def get_dossier_url(self): return self.dossier_oguid.get_url() def get_dossier(self): return self.dossier_oguid.resolve_object()
def get_overview_attributes(self): model = self.load_model() assert model, 'missing db-model for {}'.format(self) attributes = [ { 'label': _(u"label_title", default=u'Title'), 'value': model.title }, { 'label': _('label_committee', default=u'Committee'), 'value': model.committee.get_link(), 'is_html': True }, { 'label': _('label_meeting', default=u'Meeting'), 'value': model.get_meeting_link(), 'is_html': True }, ] if is_word_meeting_implementation_enabled(): proposal_document = self.get_proposal_document() if proposal_document: attributes.append({ 'label': _('proposal_document', default=u'Proposal document'), 'value': DocumentLinkWidget(proposal_document).render(), 'is_html': True }) else: attributes.extend([ { 'label': _('label_legal_basis', default=u'Legal basis'), 'value': model.legal_basis, 'is_html': True }, { 'label': _('label_initial_position', default=u'Initial position'), 'value': model.initial_position, 'is_html': True }, { 'label': _('label_proposed_action', default=u'Proposed action'), 'value': model.proposed_action, 'is_html': True }, { 'label': _('label_decision_draft', default=u'Decision draft'), 'value': model.decision_draft, 'is_html': True }, { 'label': _('label_decision', default=u'Decision'), 'value': model.get_decision(), 'is_html': True }, { 'label': _('label_publish_in', default=u'Publish in'), 'value': model.publish_in, 'is_html': True }, { 'label': _('label_disclose_to', default=u'Disclose to'), 'value': model.disclose_to, 'is_html': True }, { 'label': _('label_copy_for_attention', default=u'Copy for attention'), 'value': model.copy_for_attention, 'is_html': True }, ]) attributes.extend([ { 'label': _('label_workflow_state', default=u'State'), 'value': self.get_state().title, 'is_html': True }, { 'label': _('label_decision_number', default=u'Decision number'), 'value': model.get_decision_number(), 'is_html': True }, ]) return attributes
class ICommitteeContainer(model.Schema): """Base schema for a the committee container. """ protocol_template = RelationChoice( title=_('Protocol template'), source=sablon_template_source, required=True, ) protocol_header_template = RelationChoice( title=_('label_protocol_header_template', default='Protocol header template'), source=sablon_template_source, required=True, ) protocol_suffix_template = RelationChoice( title=_('label_protocol_suffix_template', default='Protocol suffix template'), source=sablon_template_source, required=False, ) agenda_item_header_template = RelationChoice( title=_('label_agenda_item_header_template', default='Agenda item header template for the protocol'), source=sablon_template_source, required=False, ) agenda_item_suffix_template = RelationChoice( title=_('label_agenda_item_suffix_template', default='Agenda item suffix template for the protocol'), source=sablon_template_source, required=False, ) excerpt_header_template = RelationChoice( title=_('label_excerpt_header_template', default='Excerpt header template'), source=sablon_template_source, required=False, ) excerpt_suffix_template = RelationChoice( title=_('label_excerpt_suffix_template', default='Excerpt suffix template'), source=sablon_template_source, required=False, ) excerpt_template = RelationChoice( title=_('Excerpt template'), source=sablon_template_source, required=True, ) agendaitem_list_template = RelationChoice( title=_('label_agendaitem_list_template', default=u'Agendaitem list template'), source=sablon_template_source, required=False, ) toc_template = RelationChoice( title=_('label_toc_template', default=u'Table of contents template'), source=sablon_template_source, required=False, ) ad_hoc_template = RelationChoice( title=_('label_ad_hoc_template', default=u'Ad hoc agenda item template'), source=proposal_template_source, required=False, ) paragraph_template = RelationChoice( title=_('label_paragraph_template', default=u'Paragraph template'), source=sablon_template_source, required=True, )
class SubmittedProposalTransitionCommentAddForm(form.AddForm, AutoExtensibleForm): allow_prefill_from_GET_request = True # XXX fields = field.Fields(ISubmittedProposalTransitionCommentFormSchema) # keep widget for converters (even though field is hidden) fields['transition'].widgetFactory = radio.RadioFieldWidget @classmethod def url_for(cls, context, transition): return '%s/addtransitioncomment_sql?form.widgets.transition=%s' % ( context.absolute_url(), transition) def execute_transition(self, transition, text=None): if self.context.contains_checked_out_documents(): msg = _( u'error_must_checkin_documents_for_transition', default= u'Cannot change the state because the proposal contains checked' u' out documents.') api.portal.show_message(message=msg, request=self.request, type='error') return self.redirect() if not self.is_valid_transition(transition): raise NotFound self.context.execute_transition(transition, text) if transition == 'submitted-pending': return self.redirect(to_parent=True) else: return self.redirect() def is_valid_transition(self, transition_name): return self.context.can_execute_transition(transition_name) def redirect(self, to_parent=False): if to_parent: url = aq_parent(aq_inner(self.context)).absolute_url() else: url = self.context.absolute_url() response = self.request.RESPONSE if response.status != 302: # only redirect if not already redirecting return response.redirect(url) @property def label(self): title = self.context.Title().decode('utf-8') transition = self.context.workflow.get_transition(self.transition) transition_title = translate(transition.title, domain='plone', context=self.request) return u'{}: {}'.format(title, transition_title) @property def transition(self): if not hasattr(self, '_transition'): self._transition = self.request.get('form.widgets.transition', self.request.get('transition')) if not self._transition: raise BadRequest("A transition is required") return self._transition def updateWidgets(self): super(SubmittedProposalTransitionCommentAddForm, self).updateWidgets() self.widgets['transition'].mode = HIDDEN_MODE def updateActions(self): super(SubmittedProposalTransitionCommentAddForm, self).updateActions() self.actions["save"].addClass("context") @button.buttonAndHandler(_(u'button_confirm', default='Confirm'), name='save') def handleSubmit(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return transition = data['transition'] comment = data.get('text') return self.execute_transition(transition, comment) @button.buttonAndHandler( _(u'button_cancel', default='Cancel'), name='cancel', ) def handleCancel(self, action): return self.request.RESPONSE.redirect('.')
def add_protocol_type(self): self.data['protocol'] = { 'type': translate(_(u'label_agendaitem_list', default=u'Agendaitem list'), context=getRequest()) }
class ICommittee(model.Schema): """Base schema for the committee. """ model.fieldset( u'protocol', label=_(u'fieldset_protocol_templates', u'Protocol templates'), fields=[ 'protocol_header_template', 'paragraph_template', 'agenda_item_header_template', 'agenda_item_suffix_template', 'protocol_suffix_template', ], ) model.fieldset( u'excerpt', label=_(u'fieldset_excerpt_templates', u'Excerpt templates'), fields=[ 'excerpt_header_template', 'excerpt_suffix_template', ], ) repository_folder = RelationChoice( title=_(u'Linked repository folder'), description=_( u'label_linked_repository_folder', default=u'Contains automatically generated dossiers and documents ' u'for this committee.'), source=repository_folder_source, required=True) protocol_header_template = RelationChoice( title=_('label_protocol_header_template', default='Protocol header template'), source=sablon_template_source, required=True, ) protocol_suffix_template = RelationChoice( title=_('label_protocol_suffix_template', default='Protocol suffix template'), source=sablon_template_source, required=False, ) agenda_item_header_template = RelationChoice( title=_('label_agenda_item_header_template', default='Agenda item header template for the protocol'), source=sablon_template_source, required=False, ) agenda_item_suffix_template = RelationChoice( title=_('label_agenda_item_suffix_template', default='Agenda item suffix template for the protocol'), source=sablon_template_source, required=False, ) excerpt_header_template = RelationChoice( title=_('label_excerpt_header_template', default='Excerpt header template'), source=sablon_template_source, required=False, ) excerpt_suffix_template = RelationChoice( title=_('label_excerpt_suffix_template', default='Excerpt suffix template'), source=sablon_template_source, required=False, ) agendaitem_list_template = RelationChoice( title=_('label_agendaitem_list_template', default=u'Agendaitem list template'), source=sablon_template_source, required=False, ) toc_template = RelationChoice( title=_('label_toc_template', default=u'Table of contents template'), source=sablon_template_source, required=False, ) paragraph_template = RelationChoice( title=_('label_paragraph_template', default=u'Paragraph template'), source=sablon_template_source, required=False, ) form.widget('allowed_proposal_templates', CheckBoxFieldWidget) allowed_proposal_templates = schema.List( title=_(u'label_allowed_proposal_templates', default=u'Allowed proposal templates'), description=_(u'help_allowed_proposal_templates', default=u'Select the proposal templates allowed for' u' this commitee, or select no templates for allowing' u' all templates.'), value_type=schema.Choice( source='opengever.meeting.ProposalTemplatesVocabulary'), required=False, default=None, missing_value=None) form.widget('allowed_ad_hoc_agenda_item_templates', CheckBoxFieldWidget) allowed_ad_hoc_agenda_item_templates = schema.List( title=_(u'label_allowed_ad_hoc_agenda_item_templates', default=u'Allowed ad-hoc agenda item templates'), description=_(u'help_allowed_ad_hoc_agenda_item_templates', default=u'Select the ad-hoc agenda item templates' u' allowed for this commitee, or select no' u' templates for allowing all templates.'), value_type=schema.Choice( source='opengever.meeting.ProposalTemplatesVocabulary'), required=False, default=None, missing_value=None) ad_hoc_template = RelationChoice( title=_('label_ad_hoc_template', default=u'Ad hoc agenda item template'), source=proposal_template_source, required=False, ) @invariant def default_template_is_in_allowed_templates(data): """ Validate ad-hoc agenda item templates """ default_template = data.ad_hoc_template allowed_templates = data.allowed_ad_hoc_agenda_item_templates if default_template is None: return if not allowed_templates: return if IUUID(default_template) not in allowed_templates: raise Invalid( _(u'error_default_template_is_in_allowed_templates', default=u'The default ad-hoc agenda item template has to be ' u'amongst the allowed ones for this committee.'))
from opengever.meeting.form import ModelProxyEditForm from opengever.meeting.model import Period from plone.dexterity.browser import add from plone.dexterity.browser import edit from plone.dexterity.interfaces import IDexterityFTI from plone.z3cform.layout import FormWrapper from Products.CMFCore.interfaces import IFolderish from z3c.form import field from z3c.form.button import buttonAndHandler from z3c.form.field import Fields from z3c.form.interfaces import IDataConverter from zope.component import adapter from zope.component import getUtility from zope.publisher.interfaces.browser import IDefaultBrowserLayer ADD_COMMITTEE_STEPS = (('add-committee', _(u'Add committee')), ('add-period', _(u'Add period'))) def get_dm_key(context): container_oguid = Oguid.for_object(context) return 'create_committee:{}'.format(container_oguid) class AddForm(BaseWizardStepForm, add.DefaultAddForm): """Form to create a committee. Is registered as default add form for committees. Does not create the committee when submitted but store its data in IIWizardStorage. Then redirects to the second step to add an initial period.
def get_excerpt_title(self): return self._get_title(_("Protocol Excerpt"))
def get_protocol_title(self): return self._get_title(_("Protocol"))
def message(self): return _(u'proposal_history_label_revised', u'Proposal revised by ${user}', mapping={'user': self.get_actor_link()})
def message(self): return _(u'proposal_history_label_scheduled', u'Scheduled for meeting ${meeting} by ${user}', mapping={'user': self.get_actor_link(), 'meeting': self.meeting_title})
def message(self): return _(u'proposal_history_label_document_submitted', u'Document ${title} submitted in version ${version} by ${user}', mapping={'user': self.get_actor_link(), 'title': self.document_title or '', 'version': self.submitted_version})
class AgendaItem(Base): """An item must either have a reference to a proposal or a title. """ __tablename__ = 'agendaitems' # workflow definition STATE_PENDING = State('pending', is_default=True, title=_('pending', default='Pending')) STATE_DECIDED = State('decided', title=_('decided', default='Decided')) STATE_REVISION = State('revision', title=_('revision', default='Revision')) workflow = Workflow([STATE_PENDING, STATE_DECIDED, STATE_REVISION], [ Transition('pending', 'decided', title=_('decide', default='Decide')), Transition('decided', 'revision', title=_('reopen', default='Reopen')), Transition('revision', 'decided', title=_('revise', default='Revise')), ]) agenda_item_id = Column("id", Integer, Sequence("agendaitems_id_seq"), primary_key=True) workflow_state = Column(String(WORKFLOW_STATE_LENGTH), nullable=False, default=workflow.default_state.name) proposal_id = Column(Integer, ForeignKey('proposals.id')) proposal = relationship("Proposal", uselist=False, backref=backref('agenda_item', uselist=False)) decision_number = Column(Integer) title = Column(UnicodeCoercingText) number = Column('item_number', String(16)) is_paragraph = Column(Boolean, nullable=False, default=False) sort_order = Column(Integer, nullable=False, default=0) meeting_id = Column(Integer, ForeignKey('meetings.id'), nullable=False) discussion = Column(UnicodeCoercingText) decision = Column(UnicodeCoercingText) def __init__(self, *args, **kwargs): """Prefill the decision attributes with proposal's decision_draft. """ proposal = kwargs.get('proposal') if proposal and not kwargs.get('decision'): kwargs.update({'decision': proposal.decision_draft}) super(AgendaItem, self).__init__(*args, **kwargs) def update(self, request): """Update with changed data.""" data = request.get(self.name) if not data: return def to_safe_html(markup): # keep empty data (whatever it is), it makes transform unhappy if not markup: return markup markup = markup.decode('utf-8') markup = trix2sablon.convert(markup) return trix_strip_whitespace(markup) if self.has_proposal: self.proposal.legal_basis = to_safe_html(data.get('legal_basis')) self.proposal.initial_position = to_safe_html( data.get('initial_position')) self.proposal.considerations = to_safe_html( data.get('considerations')) self.proposal.proposed_action = to_safe_html( data.get('proposed_action')) self.proposal.publish_in = to_safe_html(data.get('publish_in')) self.proposal.disclose_to = to_safe_html(data.get('disclose_to')) self.proposal.copy_for_attention = to_safe_html( data.get('copy_for_attention')) self.discussion = to_safe_html(data.get('discussion')) self.decision = to_safe_html(data.get('decision')) def get_field_data(self, include_initial_position=True, include_legal_basis=True, include_considerations=True, include_proposed_action=True, include_discussion=True, include_decision=True, include_publish_in=True, include_disclose_to=True, include_copy_for_attention=True): data = { 'number': self.number, 'description': self.description, 'title': self.get_title(), 'dossier_reference_number': self.get_dossier_reference_number(), 'repository_folder_title': self.get_repository_folder_title(), 'is_paragraph': self.is_paragraph, 'decision_number': self.decision_number, 'html:decision_draft': self._sanitize_text(self.get_decision_draft()) } if include_initial_position: data['html:initial_position'] = self._sanitize_text( self.initial_position) if include_legal_basis: data['html:legal_basis'] = self._sanitize_text(self.legal_basis) if include_considerations: data['html:considerations'] = self._sanitize_text( self.considerations) if include_proposed_action: data['html:proposed_action'] = self._sanitize_text( self.proposed_action) if include_discussion: data['html:discussion'] = self._sanitize_text(self.discussion) if include_decision: data['html:decision'] = self._sanitize_text(self.decision) if include_publish_in: data['html:publish_in'] = self._sanitize_text(self.publish_in) if include_disclose_to: data['html:disclose_to'] = self._sanitize_text(self.disclose_to) if include_copy_for_attention: data['html:copy_for_attention'] = self._sanitize_text( self.copy_for_attention) self._add_attachment_data(data) return data def _add_attachment_data(self, data): if not self.has_proposal: return documents = self.proposal.resolve_submitted_documents() if not documents: return attachment_data = [] for document in documents: attachment = {'title': document.title} filename = document.get_filename() if filename: attachment['filename'] = filename attachment_data.append(attachment) data['attachments'] = attachment_data def _sanitize_text(self, text): if not text: return None return text def get_title(self, include_number=False): title = self.proposal.title if self.has_proposal else self.title if include_number and self.number: title = u"{} {}".format(self.number, title) return title def set_title(self, title): if self.has_proposal: self.proposal.title = title else: self.title = title def get_decision_draft(self): if self.has_proposal: return self.proposal.decision_draft def get_dossier_reference_number(self): if self.has_proposal: return self.proposal.dossier_reference_number return None def get_repository_folder_title(self): if self.has_proposal: return self.proposal.repository_folder_title return None def get_css_class(self): css_classes = [] if self.is_paragraph: css_classes.append("paragraph") if self.has_submitted_documents(): css_classes.append("expandable") if self.has_proposal: css_classes.append("proposal") return " ".join(css_classes) def get_state(self): return self.workflow.get_state(self.workflow_state) def generate_decision_number(self, period): if self.is_paragraph: return next_decision_number = period.get_next_decision_sequence_number() self.decision_number = next_decision_number def remove(self): assert self.meeting.is_editable() session = create_session() if self.proposal: self.proposal.remove_scheduled(self.meeting) session.delete(self) self.meeting.reorder_agenda_items() def get_proposal_link(self, include_icon=True): if not self.has_proposal: return self.get_title() return self.proposal.get_submitted_link(include_icon=include_icon) def serialize(self): return { 'id': self.agenda_item_id, 'css_class': self.get_css_class(), 'title': self.get_title(), 'number': self.number, 'has_proposal': self.has_proposal, 'link': self.get_proposal_link(include_icon=False), } @property def has_proposal(self): return self.proposal is not None @property def legal_basis(self): return self.proposal.legal_basis if self.has_proposal else None @property def initial_position(self): return self.proposal.initial_position if self.has_proposal else None @property def considerations(self): return self.proposal.considerations if self.has_proposal else None @property def proposed_action(self): return self.proposal.proposed_action if self.has_proposal else None @property def publish_in(self): return self.proposal.publish_in if self.has_proposal else None @property def disclose_to(self): return self.proposal.disclose_to if self.has_proposal else None @property def copy_for_attention(self): return self.proposal.copy_for_attention if self.has_proposal else None @property def name(self): """Currently used as name for input tags in html.""" return "agenda_item-{}".format(self.agenda_item_id) @property def description(self): return self.get_title() def has_submitted_documents(self): return self.has_proposal and self.proposal.has_submitted_documents() def has_submitted_excerpt_document(self): return self.has_proposal and self.proposal.has_submitted_excerpt_document( ) def close(self): """Close the agenda item. Can be called to close an agenda item, this puts the agenda item in decided state using the correct transitions. Currently valid states are: decided: do nothing pending: decide revision: revise """ if self.is_revise_possible(): self.revise() self.decide() def is_decide_possible(self): if not self.is_paragraph: return self.get_state() == self.STATE_PENDING return False def is_decided(self): if not self.is_paragraph: return self.get_state() == self.STATE_DECIDED return False def decide(self): if self.get_state() == self.STATE_DECIDED: return self.meeting.hold() if self.has_proposal: self.proposal.decide(self) self.workflow.execute_transition(None, self, 'pending-decided') def reopen(self): if self.has_proposal: self.proposal.reopen(self) self.workflow.execute_transition(None, self, 'decided-revision') def is_reopen_possible(self): if not self.is_paragraph: return self.get_state() == self.STATE_DECIDED return False def revise(self): assert self.is_revise_possible() if self.has_proposal: self.proposal.revise(self) self.workflow.execute_transition(None, self, 'revision-decided') def is_revise_possible(self): if not self.is_paragraph: return self.get_state() == self.STATE_REVISION return False
class AddInitialPeriodStep(BaseWizardStepForm, ModelProxyAddForm, add.DefaultAddForm): """Form to add an initial period during committee creation. Second part of a two-step wizard. Loads data from IIWizardStorage to also create the committee from the first step after successful submission. This form extends from dexterity's DefaultAddForm to create the plone content-type easily. To avoid duplicating DefaultAddForm.create we swap this form's fields during createAndAdd, i.e. after validation was successful. Does not extend from dexterity.AddForm to avoid auto-registering this form as add-form for committees, this would conflict with the form from the first step. Instead configure portal_type manually. """ step_name = 'add-period' label = _('Add period') steps = ADD_COMMITTEE_STEPS portal_type = 'opengever.meeting.committee' content_type = Committee schema = IPeriodModel fields = Fields(IPeriodModel) fields['date_from'].widgetFactory = DatePickerFieldWidget fields['date_to'].widgetFactory = DatePickerFieldWidget @property def additionalSchemata(self): return [] def updateWidgets(self): super(AddInitialPeriodStep, self).updateWidgets() self.inject_default_values() def inject_default_values(self): today = date.today() title = self.widgets['title'] date_from = self.widgets['date_from'] date_to = self.widgets['date_to'] if not title.value: title.value = unicode(today.year) if not date_from.value: date_from.value = IDataConverter(date_from).toWidgetValue( date(today.year, 1, 1)) if not date_to.value: date_to.value = IDataConverter(date_to).toWidgetValue( date(today.year, 12, 31)) def createAndAdd(self, data): """Remember data for period and inject committee data from previous form. """ # switch fields when creating, use committee content-type fields to # create the plone object. self.fields = Fields(Committee.content_schema) dm = getUtility(IWizardDataStorage) committee_data = dm.get_data(get_dm_key(self.context)) committee = super(AddInitialPeriodStep, self).createAndAdd(committee_data) self.create_period(committee, data) dm.drop_data(get_dm_key(self.context)) return committee def create_period(self, committee, data): session = create_session() data.update({'committee': committee.load_model()}) period = Period(**data) session.add(period) session.flush() # required to immediately create an autoincremented id
class Proposal(Base): """Sql representation of a proposal.""" __tablename__ = 'proposals' __table_args__ = (UniqueConstraint('admin_unit_id', 'int_id'), UniqueConstraint('submitted_admin_unit_id', 'submitted_int_id'), {}) proposal_id = Column("id", Integer, Sequence("proposal_id_seq"), primary_key=True) admin_unit_id = Column(String(UNIT_ID_LENGTH), nullable=False) int_id = Column(Integer, nullable=False) oguid = composite(Oguid, admin_unit_id, int_id) physical_path = Column(UnicodeCoercingText, nullable=False) issuer = Column(String(USER_ID_LENGTH), nullable=False) title = Column(String(MAX_TITLE_LENGTH), index=True) submitted_title = Column(String(MAX_TITLE_LENGTH), index=True) description = Column(UnicodeCoercingText) submitted_description = Column(UnicodeCoercingText) date_of_submission = Column(Date, index=True) submitted_admin_unit_id = Column(String(UNIT_ID_LENGTH)) submitted_int_id = Column(Integer) submitted_oguid = composite(Oguid, submitted_admin_unit_id, submitted_int_id) submitted_physical_path = Column(UnicodeCoercingText) excerpt_document_id = Column(Integer, ForeignKey('generateddocuments.id')) excerpt_document = relationship( 'GeneratedExcerpt', uselist=False, backref=backref('proposal', uselist=False), primaryjoin="GeneratedExcerpt.document_id==Proposal.excerpt_document_id" ) submitted_excerpt_document_id = Column(Integer, ForeignKey('generateddocuments.id')) submitted_excerpt_document = relationship( 'GeneratedExcerpt', uselist=False, backref=backref('submitted_proposal', uselist=False), primaryjoin= "GeneratedExcerpt.document_id==Proposal.submitted_excerpt_document_id") workflow_state = Column(String(WORKFLOW_STATE_LENGTH), nullable=False) committee_id = Column(Integer, ForeignKey('committees.id')) committee = relationship('Committee', backref='proposals') dossier_reference_number = Column(UnicodeCoercingText, nullable=False) repository_folder_title = Column(UnicodeCoercingText, nullable=False) language = Column(String(8), nullable=False) __mapper_args__ = {"order_by": proposal_id} # workflow definition STATE_PENDING = State('pending', is_default=True, title=_('pending', default='Pending')) STATE_SUBMITTED = State('submitted', title=_('submitted', default='Submitted')) STATE_SCHEDULED = State('scheduled', title=_('scheduled', default='Scheduled')) STATE_DECIDED = State('decided', title=_('decided', default='Decided')) STATE_CANCELLED = State('cancelled', title=_('cancelled', default='Cancelled')) workflow = Workflow([ STATE_PENDING, STATE_SUBMITTED, STATE_SCHEDULED, STATE_DECIDED, STATE_CANCELLED, ], [ Reject('submitted', 'pending', title=_('reject', default='Reject')), Transition( 'submitted', 'scheduled', title=_('schedule', default='Schedule')), Transition('scheduled', 'submitted', title=_('un-schedule', default='Remove from schedule')), Transition('scheduled', 'decided', title=_('decide', default='Decide')), ]) # temporary mapping for plone workflow state to model workflow state WORKFLOW_STATE_TO_SQL_STATE = { 'proposal-state-active': 'pending', 'proposal-state-cancelled': 'cancelled', 'proposal-state-decided': 'decided', 'proposal-state-scheduled': 'scheduled', 'proposal-state-submitted': 'submitted', } def __repr__(self): return "<Proposal {}@{}>".format(self.int_id, self.admin_unit_id) @classmethod def create_from(cls, proposal): model = cls(oguid=Oguid.for_object(proposal), workflow_state='pending', physical_path=proposal.get_physical_path()) model.sync_with_proposal(proposal) return model def sync_with_proposal(self, proposal): """Sync self with a plone proposal instance.""" from opengever.meeting.model.committee import Committee reference_number = proposal.get_main_dossier_reference_number() repository_folder_title = safe_unicode( proposal.get_repository_folder_title()) committee = Committee.get_one( oguid=Oguid.parse(proposal.committee_oguid)) # temporarily use mapping from plone workflow state to model workflow # state workflow_state = api.content.get_state(proposal) new_sql_state = self.WORKFLOW_STATE_TO_SQL_STATE.get(workflow_state) if new_sql_state: self.workflow_state = new_sql_state self.committee = committee self.language = proposal.language self.physical_path = proposal.get_physical_path() self.dossier_reference_number = reference_number self.repository_folder_title = repository_folder_title self.title = proposal.title self.issuer = proposal.issuer self.description = proposal.description self.date_of_submission = proposal.date_of_submission def sync_with_submitted_proposal(self, submitted_proposal): """Sync self with a plone submitted proposal instance.""" self.submitted_oguid = Oguid.for_object(submitted_proposal) self.submitted_physical_path = submitted_proposal.get_physical_path() self.submitted_admin_unit_id = get_current_admin_unit().id() self.submitted_title = submitted_proposal.title self.submitted_description = submitted_proposal.description self.date_of_submission = submitted_proposal.date_of_submission def get_state(self): return self.workflow.get_state(self.workflow_state) def execute_transition(self, name, text=None): self.workflow.execute_transition(None, self, name, text=text) def get_admin_unit(self): return ogds_service().fetch_admin_unit(self.admin_unit_id) def get_submitted_admin_unit(self): return ogds_service().fetch_admin_unit(self.submitted_admin_unit_id) @property def id(self): return self.proposal_id @property def css_class(self): return 'contenttype-opengever-meeting-proposal' def get_decision_number(self): if self.agenda_item: return self.agenda_item.get_decision_number() return None def get_url(self): return self._get_url(self.get_admin_unit(), self.physical_path) def get_submitted_url(self): return self._get_url(self.get_submitted_admin_unit(), self.submitted_physical_path) def _get_url(self, admin_unit, physical_path): if not (admin_unit and physical_path): return '' return '/'.join((admin_unit.public_url, physical_path)) def get_link(self, include_icon=True): proposal_ = self.resolve_proposal() as_link = proposal_ is None or api.user.has_permission('View', obj=proposal_) return self._get_link(self.get_url(), self.title, include_icon=include_icon, as_link=as_link) def get_description(self): proposal_ = self.resolve_proposal() return proposal_.get_description() def get_submitted_description(self): proposal_ = self.resolve_submitted_proposal() return proposal_.get_description() def get_submitted_link(self, include_icon=True): proposal_ = self.resolve_submitted_proposal() as_link = proposal_ is None or api.user.has_permission('View', obj=proposal_) return self._get_link(self.get_submitted_url(), proposal_.title, include_icon=include_icon, as_link=as_link) def _get_link(self, url, title, include_icon=True, as_link=True): title = escape_html(title) if as_link: if include_icon: link = u'<a href="{0}" title="{1}" class="{2}">{1}</a>'.format( url, title, self.css_class) else: link = u'<a href="{0}" title="{1}">{1}</a>'.format(url, title) return link if include_icon: link = u'<span title="{0}" class="{1}">{0}</span>'.format( title, self.css_class) else: link = u'<span title="{0}">{0}</a>'.format(title) return link def getPath(self): """This method is required by a tabbedview.""" return self.physical_path def resolve_submitted_proposal(self): return self.submitted_oguid.resolve_object() def resolve_submitted_documents(self): return [doc.resolve_submitted() for doc in self.submitted_documents] def has_submitted_documents(self): return bool(self.submitted_documents) def resolve_excerpt_document(self): document = self.excerpt_document if document: return document.oguid.resolve_object() def has_submitted_excerpt_document(self): return self.submitted_excerpt_document is not None def resolve_submitted_excerpt_document(self): document = self.submitted_excerpt_document if document: return document.oguid.resolve_object() def can_be_scheduled(self): return self.get_state() == self.STATE_SUBMITTED def is_submit_additional_documents_allowed(self): return self.get_state() in [self.STATE_SUBMITTED, self.STATE_SCHEDULED] def is_editable_in_committee(self): return self.get_state() in [self.STATE_SUBMITTED, self.STATE_SCHEDULED] def schedule(self, meeting): assert self.can_be_scheduled() self.execute_transition('submitted-scheduled') meeting.agenda_items.append(AgendaItem(proposal=self)) meeting.reorder_agenda_items() submitted_proposal = self.resolve_submitted_proposal() ProposalScheduledActivity(submitted_proposal, getRequest(), meeting.meeting_id).record() IHistory(self.resolve_submitted_proposal()).append_record( u'scheduled', meeting_id=meeting.meeting_id) request_data = { 'data': advancedjson.dumps({ 'meeting_id': meeting.meeting_id, }) } expect_ok_response( dispatch_request(self.admin_unit_id, '@@receive-proposal-scheduled', path=self.physical_path, data=request_data), 'Unexpected response {!r} when scheduling proposal.') def reject(self, text): assert self.workflow.can_execute_transition(self, 'submitted-pending') self.submitted_physical_path = None self.submitted_admin_unit_id = None self.submitted_int_id = None self.date_of_submission = None # set workflow state directly for once, the transition is used to # redirect to a form. self.workflow_state = self.STATE_PENDING.name request_data = { 'data': advancedjson.dumps({ 'text': text, }) } expect_ok_response( dispatch_request(self.admin_unit_id, '@@receive-proposal-rejected', path=self.physical_path, data=request_data), 'Unexpected response {!r} when rejecting proposal.') def remove_scheduled(self, meeting): self.execute_transition('scheduled-submitted') IHistory(self.resolve_submitted_proposal()).append_record( u'remove_scheduled', meeting_id=meeting.meeting_id) request_data = { 'data': advancedjson.dumps({ 'meeting_id': meeting.meeting_id, }) } expect_ok_response( dispatch_request(self.admin_unit_id, '@@receive-proposal-unscheduled', path=self.physical_path, data=request_data), 'Unexpected response {!r} when unscheduling proposal.') def resolve_proposal(self): return self.oguid.resolve_object() def revise(self, agenda_item): assert self.get_state() == self.STATE_DECIDED document = self.resolve_submitted_proposal().get_proposal_document() checkout_manager = getMultiAdapter((document, document.REQUEST), ICheckinCheckoutManager) if checkout_manager.get_checked_out_by() is not None: raise ValueError( 'Cannot revise proposal when proposal document is checked out.' ) IHistory(self.resolve_submitted_proposal()).append_record(u'revised') def is_decided(self): return self.get_state() == self.STATE_DECIDED def reopen(self, agenda_item): assert self.is_decided() IHistory(self.resolve_submitted_proposal()).append_record(u'reopened') def decide(self, agenda_item, excerpt_document): # Proposals for AgendaItems that were decided before we introduced that # Proposals get decided only when the excerpt is returned can be # already decided even if the proposal has not been returned. Thus we # only decide the proposal if it has not been decided yet. if not self.is_decided(): proposal_document = self.resolve_submitted_proposal( ).get_proposal_document() checkout_manager = getMultiAdapter( (proposal_document, proposal_document.REQUEST), ICheckinCheckoutManager) if checkout_manager.get_checked_out_by() is not None: raise ValueError( 'Cannot decide proposal when proposal document is checked out.' ) submitted_proposal = self.resolve_submitted_proposal() ProposalDecideActivity(submitted_proposal, getRequest()).record() IHistory(submitted_proposal).append_record(u'decided') self.execute_transition('scheduled-decided') self._return_excerpt(excerpt_document) def register_excerpt(self, document_intid): """Adds a GeneratedExcerpt database entry and a corresponding proposalhistory entry. """ version = self.submitted_excerpt_document.generated_version excerpt = GeneratedExcerpt(admin_unit_id=self.admin_unit_id, int_id=document_intid, generated_version=version) self.session.add(excerpt) self.excerpt_document = excerpt def _return_excerpt(self, document): """Return the selected excerpt to the proposals originating dossier. The document is registered as official excerpt for this proposal and copied to the dossier. Future edits in the excerpt document will be synced to the proposals dossier. """ assert document in self.resolve_submitted_proposal().get_excerpts() version = document.get_current_version_id(missing_as_zero=True) excerpt = GeneratedExcerpt(oguid=Oguid.for_object(document), generated_version=version) self.submitted_excerpt_document = excerpt document_intid = self._copy_excerpt_to_proposal_dossier() self.register_excerpt(document_intid) def _copy_excerpt_to_proposal_dossier(self): """Copies the submitted excerpt to the source dossier and returns the intid of the created document. """ from opengever.meeting.command import CreateExcerptCommand response = CreateExcerptCommand( self.resolve_submitted_excerpt_document(), self.admin_unit_id, self.physical_path).execute() return response['intid'] def get_meeting_link(self): agenda_item = self.agenda_item if not agenda_item: return u'' return agenda_item.meeting.get_link()
def summary(self): return self.translate_to_all_languages( _('proposal_history_label_remove_scheduled', u'Removed from schedule of meeting ${meeting} by ${user}', mapping={'meeting': self.get_meeting_title(self.meeting_id), 'user': actor_link()}))
class IProposalTemplate(IDocumentSchema): model.primary('file') file = NamedBlobFile(title=_(u'label_file', default='File'), required=True)
class Committee(Base): __tablename__ = 'committees' __table_args__ = (UniqueConstraint('admin_unit_id', 'int_id'), {}) STATE_ACTIVE = State('active', is_default=True, title=_('active', default='Active')) STATE_INACTIVE = State('inactive', title=_('inactive', default='Inactive')) workflow = Workflow( [STATE_ACTIVE, STATE_INACTIVE], [ Transition('active', 'inactive', title=_('label_deactivate', default='Deactivate committee'), visible=False), Transition('inactive', 'active', title=_('label_reactivate', default='Reactivate committee'), visible=False) ], ) committee_id = Column("id", Integer, Sequence("committee_id_seq"), primary_key=True) group_id = Column(String(GROUP_ID_LENGTH), nullable=False) admin_unit_id = Column(String(UNIT_ID_LENGTH), nullable=False) int_id = Column(Integer, nullable=False) oguid = composite(Oguid, admin_unit_id, int_id) title = Column(String(256), index=True) physical_path = Column(String(256), nullable=False) workflow_state = Column(String(WORKFLOW_STATE_LENGTH), nullable=False, default=workflow.default_state.name) def __repr__(self): return '<Committee {}>'.format(repr(self.title)) def is_active(self): return self.get_state() == self.STATE_ACTIVE def get_admin_unit(self): return ogds_service().fetch_admin_unit(self.admin_unit_id) def get_link(self): url = self.get_url() if not url: return '' link = u'<a href="{0}" title="{1}">{1}</a>'.format( url, escape_html(self.title)) return link def get_url(self, admin_unit=None): admin_unit = admin_unit or self.get_admin_unit() if not admin_unit: return None return '/'.join((admin_unit.public_url, self.physical_path)) def get_state(self): return self.workflow.get_state(self.workflow_state) def resolve_committee(self): return self.oguid.resolve_object() def get_protocol_template(self): return self.resolve_committee().get_protocol_template() def get_protocol_header_template(self): return self.resolve_committee().get_protocol_header_template() def get_protocol_suffix_template(self): return self.resolve_committee().get_protocol_suffix_template() def get_agenda_item_header_template(self): return self.resolve_committee().get_agenda_item_header_template() def get_agenda_item_suffix_template(self): return self.resolve_committee().get_agenda_item_suffix_template() def get_excerpt_header_template(self): return self.resolve_committee().get_excerpt_header_template() def get_excerpt_suffix_template(self): return self.resolve_committee().get_excerpt_suffix_template() def get_excerpt_template(self): return self.resolve_committee().get_excerpt_template() def get_agendaitem_list_template(self): return self.resolve_committee().get_agendaitem_list_template() def get_toc_template(self): return self.resolve_committee().get_toc_template() def get_active_memberships(self): return Membership.query.filter_by(committee=self).only_active() def get_active_members(self): return (Member.query.join(Member.memberships).options( joinedload(Member.memberships)).filter( and_(Membership.committee == self, Membership.date_from <= date.today(), Membership.date_to >= date.today())).order_by( Member.lastname)) def deactivate(self): if self.has_pending_meetings() or self.has_unscheduled_proposals(): return False return self.workflow.execute_transition(None, self, 'active-inactive') def reactivate(self): return self.workflow.execute_transition(None, self, 'inactive-active') def check_deactivate_conditions(self): conditions = [self.all_meetings_closed, self.no_unscheduled_proposals] for condition in conditions: if not condition(): return False return True def has_pending_meetings(self): return bool(Meeting.query.pending_meetings(self).count()) def has_unscheduled_proposals(self): query = Proposal.query.filter_by( committee=self, workflow_state=Proposal.STATE_SUBMITTED.name) return bool(query.count())
def add_protocol_type(self): self.data['protocol'] = { 'type': translate(_(u'protocol', default=u'Protocol'), context=getRequest()) }
def success_message(self): return _('msg_successfully_deleted', default=u'The object was deleted successfully.')
def execute(self, obj, model, text=None, **kwargs): obj.reject(text) msg = _(u"The proposal has been rejected successfully") api.portal.show_message(msg, request=getRequest(), type='info')
class ProposalTransitionCommentAddForm(form.AddForm, AutoExtensibleForm): allow_prefill_from_GET_request = True # XXX fields = field.Fields(IProposalTransitionCommentFormSchema) # keep widget for converters (even though field is hidden) fields['transition'].widgetFactory = radio.RadioFieldWidget @button.buttonAndHandler(_(u'button_confirm', default='Confirm'), name='save') def handleSubmit(self, action): data, errors = self.extractData() if errors: return comment = data.get('text') wftool = api.portal.get_tool('portal_workflow') wftool.doActionFor(self.context, self.transition, comment=comment, transition_params=data) return self.redirect() @button.buttonAndHandler( _(u'button_cancel', default='Cancel'), name='cancel', ) def handleCancel(self, action): return self.request.RESPONSE.redirect('.') def updateWidgets(self): super(ProposalTransitionCommentAddForm, self).updateWidgets() self.widgets['transition'].mode = HIDDEN_MODE def updateActions(self): super(ProposalTransitionCommentAddForm, self).updateActions() self.actions["save"].addClass("context") @property def transition(self): # Ignore unauthorized requests (called by the contenttree widget) if api.user.get_current() == nobody: return if not hasattr(self, '_transition'): self._transition = self.request.get('form.widgets.transition', self.request.get('transition')) if not self._transition: raise BadRequest("A transition is required") return self._transition @property def label(self): label = self.context.Title().decode('utf-8') transition = translate(self.transition, domain='plone', context=self.request) return u'{}: {}'.format(label, transition) def redirect(self): msg = _(u'msg_transition_successful', default=u'Review state changed successfully.') api.portal.show_message(msg, request=self.request, type='info') url = self.context.absolute_url() response = self.request.RESPONSE if response.status != 302: # only redirect if not already redirecting return response.redirect(url)
def summary(self): return self.translate_to_all_languages( _(u'proposal_activity_label_document_submitted', u'Document ${title} submitted', mapping={'title': self.document_title or ''}))
def summary(self): return self.translate_to_all_languages( _('proposal_history_label_rejected', u'Rejected by ${user}', mapping={'user': actor_link()}))
class Meeting(Base, SQLFormSupport): STATE_PENDING = State('pending', is_default=True, title=_('pending', default='Pending')) STATE_HELD = State('held', title=_('held', default='Held')) STATE_CLOSED = State('closed', title=_('closed', default='Closed')) workflow = Workflow( [STATE_PENDING, STATE_HELD, STATE_CLOSED], [ CloseTransition('pending', 'closed', title=_('close_meeting', default='Close meeting')), Transition('pending', 'held', title=_('hold', default='Hold meeting'), visible=False), CloseTransition('held', 'closed', title=_('close_meeting', default='Close meeting')), Transition('closed', 'held', title=_('reopen', default='Reopen'), condition=is_word_meeting_implementation_enabled) ], show_in_actions_menu=True, transition_controller=MeetingTransitionController, ) __tablename__ = 'meetings' meeting_id = Column("id", Integer, Sequence("meeting_id_seq"), primary_key=True) committee_id = Column(Integer, ForeignKey('committees.id'), nullable=False) committee = relationship("Committee", backref='meetings') location = Column(String(256)) title = Column(UnicodeCoercingText) start = Column('start_datetime', UTCDateTime(timezone=True), nullable=False) end = Column('end_datetime', UTCDateTime(timezone=True)) workflow_state = Column(String(WORKFLOW_STATE_LENGTH), nullable=False, default=workflow.default_state.name) modified = Column(UTCDateTime(timezone=True), nullable=False, default=utcnow_tz_aware) meeting_number = Column(Integer) presidency = relationship( 'Member', primaryjoin="Member.member_id==Meeting.presidency_id") presidency_id = Column(Integer, ForeignKey('members.id')) secretary = relationship( 'Member', primaryjoin="Member.member_id==Meeting.secretary_id") secretary_id = Column(Integer, ForeignKey('members.id')) other_participants = Column(UnicodeCoercingText) participants = relationship('Member', secondary=meeting_participants, order_by='Member.lastname, Member.firstname', backref='meetings') dossier_admin_unit_id = Column(String(UNIT_ID_LENGTH), nullable=False) dossier_int_id = Column(Integer, nullable=False) dossier_oguid = composite(Oguid, dossier_admin_unit_id, dossier_int_id) agenda_items = relationship("AgendaItem", order_by='AgendaItem.sort_order', backref='meeting') protocol_document_id = Column(Integer, ForeignKey('generateddocuments.id')) protocol_document = relationship( 'GeneratedProtocol', uselist=False, backref=backref('meeting', uselist=False), primaryjoin= "GeneratedProtocol.document_id==Meeting.protocol_document_id") protocol_start_page_number = Column(Integer) agendaitem_list_document_id = Column(Integer, ForeignKey('generateddocuments.id')) agendaitem_list_document = relationship( 'GeneratedAgendaItemList', uselist=False, backref=backref('meeting', uselist=False), primaryjoin= "GeneratedAgendaItemList.document_id==Meeting.agendaitem_list_document_id" ) # define relationship here using a secondary table to keep # GeneratedDocument as simple as possible and avoid that it actively # knows about all its relationships excerpt_documents = relationship( 'GeneratedExcerpt', secondary=meeting_excerpts, ) def initialize_participants(self): """Set all active members of our committee as participants of this meeting. """ self.participants = [ membership.member for membership in Membership.query.for_meeting(self) ] def __repr__(self): return '<Meeting at "{}">'.format(self.start) def generate_meeting_number(self): """Generate meeting number for self. This method locks the current period of this meeting to protect its meeting_sequence_number against concurrent updates. """ period = Period.query.get_current_for_update(self.committee) self.meeting_number = period.get_next_meeting_sequence_number() def generate_decision_numbers(self): """Generate decision numbers for each agenda item of this meeting. This method locks the current period of this meeting to protect its decision_sequence_number against concurrent updates. """ period = Period.query.get_current_for_update(self.committee) for agenda_item in self.agenda_items: agenda_item.generate_decision_number(period) def update_protocol_document(self): """Update or create meeting's protocol.""" from opengever.meeting.command import CreateGeneratedDocumentCommand from opengever.meeting.command import MergeDocxProtocolCommand from opengever.meeting.command import ProtocolOperations from opengever.meeting.command import UpdateGeneratedDocumentCommand if self.has_protocol_document( ) and not self.protocol_document.is_locked(): # The protocol should never be changed when it is no longer locked: # the user probably has made changes manually. return operations = ProtocolOperations() if is_word_meeting_implementation_enabled(): command = MergeDocxProtocolCommand( self.get_dossier(), self, operations, lock_document_after_creation=True) else: if self.has_protocol_document(): command = UpdateGeneratedDocumentCommand( self.protocol_document, self, operations) else: command = CreateGeneratedDocumentCommand( self.get_dossier(), self, operations, lock_document_after_creation=True) command.execute() def unlock_protocol_document(self): if not self.protocol_document: return self.protocol_document.unlock_document() def hold(self): if self.workflow_state == 'held': return self.generate_meeting_number() self.generate_decision_numbers() self.workflow_state = 'held' def close(self): """Closes a meeting means set the meeting in the closed state. - generate and set the meeting number - generate decision numbers for each agenda_item - close each agenda item (generates proposal excerpt and change workflow state) - update and unlock the protocol document """ self.hold() for agenda_item in self.agenda_items: agenda_item.close() self.update_protocol_document() self.unlock_protocol_document() self.workflow_state = 'closed' @property def css_class(self): return 'contenttype-opengever-meeting-meeting' def is_editable(self): return self.get_state() in [self.STATE_PENDING, self.STATE_HELD] def is_agendalist_editable(self): return self.get_state() == self.STATE_PENDING def has_protocol_document(self): return self.protocol_document is not None def has_agendaitem_list_document(self): return self.agendaitem_list_document is not None @property def wrapper_id(self): return 'meeting-{}'.format(self.meeting_id) def _get_title(self, prefix): return u"{}-{}".format(translate(prefix, context=getRequest()), self.get_title()) def _get_filename(self, prefix): normalizer = getUtility(IIDNormalizer) return u"{}-{}.docx".format(translate(prefix, context=getRequest()), normalizer.normalize(self.get_title())) def get_protocol_title(self): return self._get_title(_("Protocol")) def get_excerpt_title(self): return self._get_title(_("Protocol Excerpt")) def get_agendaitem_list_title(self): return self._get_title( _(u'label_agendaitem_list', default=u'Agendaitem list')) def get_protocol_filename(self): return self._get_filename(_("Protocol")) def get_excerpt_filename(self): return self._get_filename(_("Protocol Excerpt")) def get_agendaitem_list_filename(self): return self._get_filename( _(u'label_agendaitem_list', default=u'Agendaitem list')) def get_protocol_template(self): return self.committee.get_protocol_template() def get_excerpt_template(self): return self.committee.get_excerpt_template() def get_agendaitem_list_template(self): return self.committee.get_agendaitem_list_template() @property def physical_path(self): return '/'.join((self.committee.physical_path, self.wrapper_id)) def execute_transition(self, name): self.workflow.execute_transition(self, self, name) def can_execute_transition(self, name): return self.workflow.can_execute_transition(self, name) def get_state(self): return self.workflow.get_state(self.workflow_state) def update_model(self, data): """Manually set the modified timestamp when updating meetings.""" super(Meeting, self).update_model(data) self.modified = utcnow_tz_aware() def get_title(self): return self.title def get_date(self): return api.portal.get_localized_time(datetime=self.start) def get_start(self): """Returns the start datetime in localized format. """ return api.portal.get_localized_time(datetime=self.start, long_format=True) def get_end(self): """Returns the end datetime in localized format. """ if self.end: return api.portal.get_localized_time(datetime=self.end, long_format=True) return None def get_start_time(self): return self._get_localized_time(self.start) def get_end_time(self): if not self.end: return '' return self._get_localized_time(self.end) def _get_localized_time(self, date): if not date: return '' return api.portal.get_localized_time(datetime=date, time_only=True) def schedule_proposal(self, proposal): assert proposal.committee == self.committee proposal.schedule(self) self.reorder_agenda_items() def schedule_text(self, title, is_paragraph=False): self.agenda_items.append( AgendaItem(title=title, is_paragraph=is_paragraph)) self.reorder_agenda_items() @require_word_meeting_feature def schedule_ad_hoc(self, title): committee = self.committee.resolve_committee() ad_hoc_template = committee.get_ad_hoc_template() if not ad_hoc_template: raise MissingAdHocTemplate meeting_dossier = self.get_dossier() if not api.user.get_current().checkPermission( 'opengever.document: Add document', meeting_dossier): raise MissingMeetingDossierPermissions document_title = _(u'title_ad_hoc_document', default=u'Ad hoc agenda item ${title}', mapping={u'title': title}) ad_hoc_document = CreateDocumentCommand( context=meeting_dossier, filename=ad_hoc_template.file.filename, data=ad_hoc_template.file.data, content_type=ad_hoc_template.file.contentType, title=translate(document_title, context=getRequest())).execute() agenda_item = AgendaItem(title=title, document=ad_hoc_document, is_paragraph=False) self.agenda_items.append(agenda_item) self.reorder_agenda_items() return agenda_item def _set_agenda_item_order(self, new_order): agenda_items_by_id = OrderedDict( (item.agenda_item_id, item) for item in self.agenda_items) agenda_items = [] for agenda_item_id in new_order: agenda_item = agenda_items_by_id.pop(agenda_item_id, None) if agenda_item: agenda_items.append(agenda_item) agenda_items.extend(agenda_items_by_id.values()) self.agenda_items = agenda_items def reorder_agenda_items(self, new_order=None): if new_order: self._set_agenda_item_order(new_order) sort_order = 1 number = 1 for agenda_item in self.agenda_items: agenda_item.sort_order = sort_order sort_order += 1 if not agenda_item.is_paragraph: agenda_item.number = '{}.'.format(number) number += 1 def get_submitted_link(self): return self._get_link(self.get_submitted_admin_unit(), self.submitted_physical_path) def get_link(self): url = self.get_url() link = u'<a href="{0}" title="{1}" class="{2}">{1}</a>'.format( url, escape_html(self.get_title()), self.css_class) return link def get_url(self, context=None, view='view'): elements = [ self.committee.get_admin_unit().public_url, self.physical_path ] if view: elements.append(view) return '/'.join(elements) def get_dossier_url(self): return self.dossier_oguid.get_url() def get_dossier(self): return self.dossier_oguid.resolve_object()
def summary(self): return self.translate_to_all_languages( _(u'proposal_activity_label_document_updated', u'Submitted document ${title} updated to version ${version}', mapping={'title': self.document_title or '', 'version': self.submitted_version}))
def get_excerpt_filename(self): return self._get_filename(_("Protocol Excerpt"))
def get_agendaitem_list_filename(self): return self._get_filename( _(u'label_agendaitem_list', default=u'Agendaitem list'))
required=True) form.widget(date_from=DatePickerFieldWidget) date_from = schema.Date( title=_('label_date_from', default='Start date'), required=True, ) form.widget(date_to=DatePickerFieldWidget) date_to = schema.Date( title=_('label_date_to', default='End date'), required=True, ) CLOSE_PERIOD_STEPS = (('close-period', _(u'Close period')), ('add-period', _(u'Add new period'))) def get_dm_key(committee): """Return the key used to store data in the wizard-storage.""" return 'close_period:{}'.format(committee.committee_id) class CloseCurrentPeriodStep(BaseWizardStepForm, ModelEditForm): """Form to close the current period. First part of a two-step wizard. Stores its data in IIWizardStorage when submitted.
def get_protocol_filename(self): return self._get_filename(_("Protocol"))