Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
    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()
Beispiel #4
0
    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()
Beispiel #5
0
    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')
Beispiel #7
0
    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()
Beispiel #8
0
    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()
Beispiel #9
0
    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
Beispiel #10
0
    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())
Beispiel #12
0
 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")},
     ]
Beispiel #13
0
    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()
Beispiel #14
0
 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)
Beispiel #16
0
    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},

        )
Beispiel #18
0
    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)
Beispiel #19
0
    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')
Beispiel #20
0
    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')
Beispiel #21
0
    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'),
             },
            )
Beispiel #22
0
 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')
Beispiel #23
0
    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 ()
Beispiel #24
0
    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()
Beispiel #25
0
    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
Beispiel #26
0
 def build_linked_meeting_box(self):
     box = {
         'id': 'linked_meeting',
         'content': self.linked_meeting(),
         'label': _('label_linked_meeting', default='Linked meeting')
     }
     return box
Beispiel #27
0
    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
Beispiel #28
0
    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
Beispiel #30
0
    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
Beispiel #31
0
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()
Beispiel #32
0
    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
Beispiel #33
0
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,
    )
Beispiel #34
0
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())
     }
Beispiel #36
0
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.'))
Beispiel #37
0
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.
Beispiel #38
0
 def get_excerpt_title(self):
     return self._get_title(_("Protocol Excerpt"))
Beispiel #39
0
 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})
Beispiel #43
0
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
Beispiel #44
0
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
Beispiel #45
0
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)
Beispiel #48
0
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())
     }
Beispiel #50
0
 def success_message(self):
     return _('msg_successfully_deleted',
              default=u'The object was deleted successfully.')
Beispiel #51
0
 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')
Beispiel #52
0
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()}))
Beispiel #55
0
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}))
Beispiel #57
0
 def get_excerpt_filename(self):
     return self._get_filename(_("Protocol Excerpt"))
Beispiel #58
0
 def get_agendaitem_list_filename(self):
     return self._get_filename(
         _(u'label_agendaitem_list', default=u'Agendaitem list'))
Beispiel #59
0
                            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.
Beispiel #60
0
 def get_protocol_filename(self):
     return self._get_filename(_("Protocol"))