class GroupSittingsViewlet(browser.BungeniItemsViewlet): """Display the sittings of a group. Note 1: to be able to customize the URL for each sitting, this custom viewlet replaces the previous model-introspected container listing: class GroupSittingsViewlet(SubformViewlet): sub_attr_name = "sittings" so replacing a url of the form: .../committees/obj-59/sittings/obj-17/ with: /business/sittings/obj-17 Note 2: this viewlet should probably be merged or better share the implementation at: ui.workspace.DraftSittingsViewlet !+CustomListingURL(mr, oct-2010) an alternative way to do this is to essentially stuff all the logic below into a custom column_listing to be used when listing the column -- for an example of this see how the listing of the column "owner_id" (moved by) is configured in: descriptor.ParliamentaryItemDescriptor !+ManagedContainer(mr, oct-2010) this would have been a lot simpler if the Group.sittings attribute was simply returning the list of sitting objects. """ # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#group_sittings") view_title = "Sittings" view_id = "sittings" def _get_items(self): def _format_from_to(item): start = item.start_date if start: start = dt_formatter.format(start) end = item.end_date if end: end = t_formatter.format(end) return u"%s - %s" % (start, end) dt_formatter = self.get_date_formatter("dateTime", "medium") t_formatter = self.get_date_formatter("time", "medium") def _format_venue(item): return item.venue and _(item.venue.short_name) or "" # trusted_context = removeSecurityProxy(self.context) sittings = Session().query(domain.GroupSitting ).filter(domain.GroupSitting.group == trusted_context ).order_by(domain.GroupSitting.start_date.desc()) return [{"url": "/business/sittings/obj-%s" % (item.group_sitting_id), "date_from_to": _format_from_to(item), "venue": _format_venue(item) } for item in sittings ] def update(self): self.items = self._get_items()
class MyInterestsViewlet(browser.BungeniItemsViewlet): """ Renders subscribed items """ render = z3evoque.ViewTemplateFile("workspace_myinterests.html#interests") view_id = "my-interests" view_title = _("My interests") def __init__(self, context, request, view, manager): super(MyInterestsViewlet, self).__init__(context, request, view, manager) self.site_url = absoluteURL(getSite(), self.request) def format_date(self, date): return self.get_date_formatter().format(date) def get_description(self, item): return item.description def get_title(self, item): return "%s %s %s" % (translate_obj( item.origin, self.request.locale.id.language).short_name, _(u"changes from"), self.format_date(item.date_audit)) def get_url(self, item): site = getSite() base_url = absoluteURL(site, self.request) return base_url + "/business/%ss/obj-%s" % ( item.origin.type, item.origin.parliamentary_item_id) def update(self): """ Getting necessary items """ session = Session() user = session.query(domain.User).filter( domain.User.login == self.request.principal.id).first() if user is None: self.items = [] else: # Taking latest change from each item subscriptions = [] map( lambda x: subscriptions.extend( sorted(filter( lambda change: change.action not in [u'modified', u'added'], x.changes), key=lambda x: x.date_audit, reverse=True)[:1]), user.subscriptions) # Soring all items by date subscriptions.sort(key=lambda x: x.date_audit, reverse=True) self.items = [{ 'title': self.get_title(item), 'url': self.get_url(item), 'description': self.get_description(item) } for item in subscriptions]
class WorkspaceViewletManager(WeightOrderedViewletManager): # evoque template = z3evoque.ViewTemplateFile("workspace.html#viewlet_manager") # zpt #template = ViewPageTemplateFile("templates/workspace.pt") def update(self): super(WorkspaceViewletManager, self).update()
class OfficesHeldViewlet(browser.BungeniItemsViewlet): # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#offices_held") # zpt #render = ViewPageTemplateFile("templates/offices_held_viewlet.pt") view_title = "Offices held" view_id = "offices-held" def _get_items(self): formatter = self.get_date_formatter("date", "long") trusted = removeSecurityProxy(self.context) user_id = trusted.user_id office_list = [] if interfaces.IMemberOfParliament.providedBy(self.context): parliament_id = trusted.group_id else: parliament = get_parliament_for_group_id(trusted.group_id) if parliament: parliament_id = parliament.parliament_id else: return office_list for oh in get_offices_held_for_user_in_parliament( user_id, parliament_id ): title = {} # !+FULL_NAME(mr, oct-2010) this should probably make use of # the GroupDescriptor (combined) listing Field full_name title["group"] = "%s - %s" % (_(oh[0]), oh[1] and _(oh[1]) or "") title["group_type"] = _(oh[2]) if oh[3]: title["member_title"] = _(oh[3]) else: title["member_title"] = _(u"Member") title["start_date"] = None if oh[4]: title["start_date"] = formatter.format(oh[4]) elif oh[6]: title["start_date"] = formatter.format(oh[6]) title["end_date"] = None if oh[5]: title["end_date"] = formatter.format(oh[5]) elif oh[7]: title["end_date"] = formatter.format(oh[7]) office_list.append(title) return office_list def update(self): self.items = self._get_items()
class ViewletBase(viewlet.ViewletBase): # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#items") # zpt #render = ViewPageTemplateFile("templates/workspace_item_viewlet.pt") def __init__(self, context, request, view, manager): super(ViewletBase, self).__init__(context, request, view, manager) self._data = None def getData(self): """return the data of the query.""" return self._data
class GlobalSectionsViewlet(browser.BungeniViewlet): # evoque render = z3evoque.ViewTemplateFile("navigation.html#sections") # zpt #render = ViewPageTemplateFile('templates/sections.pt') selected_portal_tab = None def update(self): context, request = self.context, self.request base_url = url.absoluteURL(getSite(), request) item_url = request.getURL() assert item_url.startswith(base_url) path = item_url[len(base_url):] self.portal_tabs = [] seen = set() menu = component.getUtility(IBrowserMenu, "site_actions") def _action_is_on_path(action): return path.startswith("/".join(action.split("/")[0:-1])) """A menu item looks like this: { 'extra': {'hideChildren': True, 'id': u''}, 'submenu': None, 'description': u'', 'title': u'Workspace', 'url': u'http://localhost:8081/', 'selected': u'selected', 'action': u'/', 'icon': None } """ for item in menu.getMenuItems(context, request): if item['action'] in seen: continue seen.add(item['action']) item['url'] = item.setdefault('url', base_url + item['action']) item['id'] = item['action'].strip('/') item['name'] = item['title'] self.portal_tabs.append(item) # take the last url-path matching action as selected_portal_tab if _action_is_on_path(item['action']): self.selected_portal_tab = item['id']
class MemberItemsViewlet(browser.BungeniItemsViewlet): """A tab with bills, motions etc for an MP (the "parliamentary activities" tab of of the "member" view) """ states = \ get_states("agendaitem", tagged=["public"]) + \ get_states("bill", not_tagged=["private"]) + \ get_states("motion", tagged=["public"]) + \ get_states("question", tagged=["public"]) + \ get_states("tableddocument", tagged=["public"]) view_title = "Parliamentary activities" view_id = "mp-items" # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#mp_items") # zpt #render = ViewPageTemplateFile("templates/mp_item_viewlet.pt") def __init__(self, context, request, view, manager): super(MemberItemsViewlet, self).__init__(context, request, view, manager) user_id = self.context.user_id parliament_id = self.context.group_id self.query = Session().query(domain.ParliamentaryItem).filter( sql.and_( domain.ParliamentaryItem.owner_id == user_id, domain.ParliamentaryItem.parliament_id == parliament_id, domain.ParliamentaryItem.status.in_(self.states), )).order_by(domain.ParliamentaryItem.parliamentary_item_id.desc()) #self.for_display = (self.query.count() > 0) self.formatter = self.get_date_formatter("date", "medium") @property def items(self): for item in self.query.all(): _url = "/business/%ss/obj-%i" % (item.type, item.parliamentary_item_id) yield { "type": item.type, "short_name": item.short_name, "status": misc.get_wf_state(item), "submission_date": item.submission_date, "url": _url }
class WorkspaceContextNavigation(StructureAwareViewlet): # evoque render = z3evoque.ViewTemplateFile("workspace.html#context_navigation") # zpt #render = ViewPageTemplateFile('templates/workspace_context_navigation.pt') def update(self): # should only ever be called for contexts with these interfaces assert (IWorkspaceContainer.providedBy(self.context) or IWorkspaceSectionContext.providedBy(self.context)) self.sections = getMenu("workspace_context_navigation", self.context, self.request) # get a translated copy of original workspace object workspace = translate_obj( misc.get_parent_with_interface(self, IWorkspaceContainer)) self.workspace_title = workspace.full_name
class GroupMembersViewlet(browser.BungeniItemsViewlet): # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#group_members") view_title = _("Members") view_id = "group-members" def _get_members(self): """Get the list of members of the context group. """ raise NotImplementedError("Must be implemented by subclass.") @property def members_container_url(self): # !+traversal(murithi, mar-2010) ideally no urls should be # hardcoded here. absoluteURL should work for memberships or # members of groups [ to improve on getting urls of children ] if IAdminSectionLayer.providedBy(self.request): trusted = removeSecurityProxy(self.context) m_container = trusted.__parent__.__parent__.parliamentmembers return url.absoluteURL(m_container, self.request) return "/members/current" def update(self): session = Session() group_members = self._get_members() mpkls = domain.MemberOfParliament formatter = self.get_date_formatter("date", "long") self.items = [{ "fullname": m.user.fullname, "url": "%s/obj-%d/" % (self.members_container_url, session.query(mpkls).filter( mpkls.user_id == m.user_id).one( ).membership_id ), "start_date": m.start_date and formatter.format(m.start_date) or None, "end_date": m.end_date and formatter.format(m.end_date) or None } for m in group_members ]
class WorkspaceContextNavigation(StructureAwareViewlet): # evoque render = z3evoque.ViewTemplateFile("workspace.html#context_navigation",) # zpt #render = ViewPageTemplateFile("templates/workspace_context_navigation.pt") def update(self): # should only ever be called for contexts with these interfaces assert (IWorkspaceContainer.providedBy(self.context) or IWorkspaceSectionContext.providedBy(self.context)) self.sections = getMenu("workspace_context_navigation", self.context, self.request) # Append a trailing slash to each workspace viewlet navigation entry so # that the right context is always maintained when using this navigation. for section in self.sections: section["url"] = url.set_url_context(section["url"]) # get a translated copy of original workspace object workspace = translate_obj( misc.get_parent_with_interface(self, IWorkspaceContainer)) self.workspace_title = workspace.full_name
class SchedulablesViewlet(browser.BungeniItemsViewlet): """Renders a portlet which calls upon the scheduling viewlet manager to render a list of schedulable items.""" view_title = _(u"Scheduling") # the instance of the ViewProvideViewletManager provide = z3evoque.ViewProvideViewletManager() # evoque render = z3evoque.ViewTemplateFile("scheduling.html#main") # zpt #render = ViewPageTemplateFile("templates/scheduling.pt") for_display = True def __init__(self, context, request, view, manager): while not ISchedulingContext.providedBy(context): context = ISchedulingContext(context, context.__parent__) if context is None: raise RuntimeError("Unable to locate a scheduling context.") super(SchedulablesViewlet, self).__init__( context, request, view, manager)
class SubformViewlet(table.AjaxContainerListing): """A container listing of the items indicated by "sub_attr_name". """ # evoque template = z3evoque.ViewTemplateFile("container.html#generic_sub") # zpt #template = ViewPageTemplateFile("templates/generic-sub-container.pt") def render(self): need("yui-datatable") return self.template() def __init__(self, context, request, view, manager): # The parent for SubformViewlets is the context (not the view) self.__parent__ = context self._context = context # !+_context(mr, oct-2010) using self.__parent__ to get to context # gives recursion error: # zope/publisher/browser.py", line 849, in __getParent # return getattr(self, '_parent', self.context) self.request = request self.manager = manager sub_attr_name = None @property def context(self): return getattr(self._context, self.sub_attr_name) @property def view_name(self): return self.sub_attr_name # self.context.__name__ @property def for_display(self): return len(self.context) > 0
class DiffView(object): # evoque template = z3evoque.ViewTemplateFile("diff.html") context = None def __init__(self, source, target, request): self.source = source self.target = target self.request = request def __call__(self, *interfaces): results = diff(self.source, self.target, *interfaces) tables = [] content_changed = False for (field, changed, hresult) in results: tables.append({ "name": field.__name__, "title": field.title, "changed": changed, "html": hresult}) if changed: content_changed = True return self.template(tables=tables, content_changed=content_changed)
class WorkflowActionViewlet(browser.BungeniBrowserView, BaseForm, viewlet.ViewletBase): """Display workflow status and actions.""" # evoque render = z3evoque.ViewTemplateFile("form.html#form") # zpt #render = ViewPageTemplateFile("templates/viewlet.pt") class IWorkflowForm(zope.interface.Interface): note = zope.schema.Text( title=_("Comment on workflow change"), required=False) date_active = zope.schema.Datetime( title=_("Active Date"), required=True) form_name = "Workflow" form_fields = form.Fields(IWorkflowForm) note_widget = TextAreaWidget note_widget.height = 1 form_fields["note"].custom_widget = note_widget form_fields["date_active"].custom_widget = TextDateTimeWidget actions = () def get_min_date_active(self): """Determine the min_date_active to validate against. """ def is_workflowed_and_draft(instance): """is item workflowed, and is so is it in a logical draft state? """ if IWorkflowed.providedBy(instance): tagged_key = instance.__class__.__name__.lower() draft_states = get_states(tagged_key, tagged=["draft"]) return instance.status in draft_states return False min_date_active = None if IAuditable.providedBy(self.context): instance = removeSecurityProxy(self.context) # !+PASTDATAENTRY(mr, jun-2011) offers a way to enter past data # for workflowed items via the UI -- note, ideally we should be # able to also control the item's creation active_date. # # If a workflowed item is in draft state, we do NOT take the # date_active of its last change as the min_date_active, but # let that min fallback to parliament's creation date... if not is_workflowed_and_draft(instance): changes = [ change for change in instance.changes if change.action == "workflow" ] if changes: # then use the "date_active" of the most recent entry min_date_active = changes[-1].date_active if not min_date_active: # fallback to current parliament's start_date (cast to a datetime) min_date_active = datetime.datetime.combine( globalsettings.get_current_parliament().start_date, datetime.time()) # As the precision of the UI-submitted datetime is only to the minute, # we adjust min_date_time by a margin of 59 secs earlier to avoid # issues of doing 2 transitions in quick succession (within same minute) # the 2nd of which could be taken to be too old... return min_date_active - datetime.timedelta(seconds=59) def validate(self, action, data): # submitted data is actually updated in following call to super.validate # !+PASTDATAENTRY(mr, jun-2011) enhancement? see Issue 612 Comment 6: # unrequire, allow active_date=None, # get_effective_active_date -> last workflow non-None active_date errors = super(WorkflowActionViewlet, self).validate(action, data) if "date_active" in data.keys(): min_date_active = self.get_min_date_active() if data.get("date_active") < min_date_active: errors.append(zope.interface.Invalid( _("Active Date is too old."))) elif data.get("date_active") > datetime.datetime.now(): errors.append(zope.interface.Invalid( _("Active Date is in the future."))) return errors def setUpWidgets(self, ignore_request=False): class WorkflowForm: note = None date_active = None self.adapters = { self.IWorkflowForm: WorkflowForm, } self.widgets = form.setUpDataWidgets( self.form_fields, self.prefix, self.context, self.request, ignore_request=ignore_request) def update(self, transition=None): # !+RENAME(mr, apr-2011) should be transition_id workflow = interfaces.IWorkflow(self.context) if transition is not None: state_transition = workflow.get_transition(transition) state_title = translate(_bc(state_transition.title), context=self.request) self.status = translate(_( u"Confirmation required for workflow transition: '${title}'", mapping={"title": state_title}), context = self.request) self.setupActions(transition) if not self.actions: self.form_fields = self.form_fields.omit("note", "date_active") elif not IAuditable.providedBy(self.context): self.form_fields = self.form_fields.omit("note", "date_active") super(WorkflowActionViewlet, self).update() def setupActions(self, transition): # !+RENAME(mr, apr-2011) should be transition_id wfc = interfaces.IWorkflowController(self.context) if transition is None: transitions = wfc.getManualTransitionIds() else: transitions = (transition,) self.actions = bindTransitions(self, transitions, None, wfc.workflow) if IWorkspaceContainer.providedBy(self.context.__parent__): self._next_url = absoluteURL(self.context.__parent__, self.request)
class WorkflowActionViewlet(browser.BungeniBrowserView, BaseForm, viewlet.ViewletBase): """Display workflow status and actions.""" # evoque render = z3evoque.ViewTemplateFile("form.html#form") # zpt #render = ViewPageTemplateFile("templates/viewlet.pt") class IWorkflowForm(zope.interface.Interface): note = zope.schema.Text(title=_("Comment on workflow change"), required=False) date_active = zope.schema.Datetime(title=_("Active Date"), required=True) form_name = "Workflow" form_fields = form.Fields(IWorkflowForm) note_widget = TextAreaWidget note_widget.height = 1 form_fields["note"].custom_widget = note_widget form_fields["date_active"].custom_widget = TextDateTimeWidget actions = () def get_min_date_active(self): """Determine the min_date_active to validate against. """ min_date_active = None if IAuditable.providedBy(self.context): instance = removeSecurityProxy(self.context) changes = [ change for change in instance.changes if change.action == "workflow" ] if changes: # then use the "date_active" of the most recent entry min_date_active = changes[-1].date_active if not min_date_active: # then fallback to the current parliament's atart_date min_date_active = globalsettings.get_current_parliament( ).start_date # As the precision of the UI-submitted datetime is only to the minute, # we adjust min_date_time by a margin of 59 secs earlier to avoid # issues of doing 2 transitions in quick succession (within same minute) # the 2nd of which could be taken to be too old... return min_date_active - timedelta(seconds=59) def validate(self, action, data): # submitted data is actually updated in following call to super.validate errors = super(WorkflowActionViewlet, self).validate(action, data) if "date_active" in data.keys(): min_date_active = self.get_min_date_active() if data.get("date_active") < min_date_active: errors.append( zope.interface.Invalid(_("Active Date is too old."))) elif data.get("date_active") > datetime.now(): errors.append( zope.interface.Invalid(_("Active Date is in the future."))) return errors def setUpWidgets(self, ignore_request=False): class WorkflowForm: note = None date_active = None self.adapters = { self.IWorkflowForm: WorkflowForm, } self.widgets = form.setUpDataWidgets(self.form_fields, self.prefix, self.context, self.request, ignore_request=ignore_request) def update(self, transition=None): wf = interfaces.IWorkflow(self.context) if transition is not None: state_transition = wf.getTransitionById(transition) # !- workflow state title translations are in bungeni.core # use the right factory here to get translation state_title = translate(_bc(state_transition.title), context=self.request) self.status = translate(_( u"Confirmation required for workflow transition: '${title}'", mapping={"title": state_title}), context=self.request) self.setupActions(transition) if not self.actions: self.form_fields = self.form_fields.omit("note", "date_active") elif not IAuditable.providedBy(self.context): self.form_fields = self.form_fields.omit("note", "date_active") super(WorkflowActionViewlet, self).update() def setupActions(self, transition): self.wf = interfaces.IWorkflowInfo(self.context) if transition is None: transitions = self.wf.getManualTransitionIds() else: transitions = (transition, ) self.actions = bindTransitions(self, transitions, None, interfaces.IWorkflow(self.context))
class SchedulableItemsViewlet(browser.BungeniItemsViewlet): """Renders a list of schedulable items for a particular ``model``, filtered by workflow ``states``. Must subclass. """ model = states = container = view_title = None # evoque render = z3evoque.ViewTemplateFile("scheduling.html#items") # zpt #render = ViewPageTemplateFile("templates/schedulable_items.pt") @property def app(self): parent = self.context.__parent__ while parent is not None: if IBungeniApplication.providedBy(parent): return parent parent = parent.__parent__ raise ValueError("Unable to locate application.") def group(self): parent = self.context.__parent__ while parent is not None: if IBungeniGroup.providedBy(parent): return parent parent = parent.__parent__ return None raise ValueError("Unable to locate application.") @property def visible(self): return not(ICommittee.providedBy(self.group())) def _query_items(self): return tuple(Session().query(self.model).filter( self.model.status.in_(self.states))) def _item_date(self, item): return item.status_date def _item_url(self, item): return url.set_url_context("%s/business/%ss/obj-%s" % ( url.absoluteURL(getSite(), self.request), item.type, item.parliamentary_item_id)) def update(self): need("yui-dragdrop") need("yui-container") sitting = self._parent._parent.context scheduled_item_ids = [item.item_id for item in sitting.item_schedule] # add location to items gsm = component.getSiteManager() adapter = gsm.adapters.lookup( (interface.implementedBy(self.model), interface.providedBy(self)), ILocation ) date_formatter = self.get_date_formatter("date", "medium") items = [ adapter(item, None) for item in self._query_items() ] # for each item, format dictionary for use in template self.items = [{ "title": properties.title, "name": item.__class__.__name__, "description": properties.description, #"date": _(u"$F", mapping={"F": # datetimedict.fromdatetime(item.changes[-1].date)}), #"date":item.changes[-1].date, # not every item has a auditlog (headings) # use last status change instead. "date": date_formatter.format(self._item_date(item)), "state": IWorkflow(item).get_state(item.status).title, "id": item.parliamentary_item_id, "class": ( (item.parliamentary_item_id in scheduled_item_ids and "dd-disable") or ""), "url": self._item_url(item), "type": item.type } for item, properties in [ (item, (IDCDescriptiveProperties.providedBy(item) and item or IDCDescriptiveProperties(item))) for item in items ] ]
class BungeniAttributeDisplay(DynamicFields, form.SubPageDisplayForm, browser.BungeniViewlet): """bungeni.subform.manager """ # evoque render = z3evoque.ViewTemplateFile("form.html#display") # zpt #render = ViewPageTemplateFile("templates/display_form.pt") # the instance of the ViewProvideViewletManager provide = z3evoque.ViewProvideViewletManager( default_provider_name="bungeni.subform.manager") mode = "view" form_name = _(u"General") view_id = "display-item" has_data = True adapters = None def get_note(self): """Return Notes if supplied by context. """ context = removeSecurityProxy(self.context) if getattr(context, "note", False): return context.note def setupActions(self): return # !+ ?? wfc = interfaces.IWorkflowController(self.context, None) if wfc is not None: transitions = wfc.getManualTransitionIds() self.actions = tuple( bindTransitions(self, transitions, wfc.workflow)) def setUpWidgets(self, ignore_request=False): languages = get_all_languages() self.form_fields = filterFields(self.context, self.form_fields) #do not display empty form fields omit_names = [] for f in self.form_fields: val = getattr(self.context, f.__name__) if val is None: omit_names.append(f.__name__) self.form_fields = self.form_fields.omit(*omit_names) context = self.context if ITranslatable.providedBy(self.context): lang = self.request.locale.getLocaleID() try: translation = get_translation_for(self.context, lang) except: translation = [] if (not translation and getattr(self.context, "language", None) and getattr(self.context, "language", None) != lang): supported_lang = languages.get(lang) if supported_lang: langname = supported_lang.get("native", None) if langname == None: langname = supported_lang.get("name") self.status = translate( _(u"This content is not yet translated into" +\ " $language", mapping={"language": langname}), domain="bungeni", context=self.request ) context = copy(removeSecurityProxy(self.context)) for field_translation in translation: setattr(context, field_translation.field_name, field_translation.field_text) self.widgets = form.setUpEditWidgets(self.form_fields, self.prefix, context, self.request, adapters=self.adapters, for_display=True, ignore_request=ignore_request) def update(self): self.setupActions() #super(BungeniAttributeDisplay, self).update() DynamicFields.update(self) self.setupActions() # after we transition we have different actions try: self.wf_status = interfaces.IStateController( removeSecurityProxy(self.context)).get_status() except: pass @property def form_name(self): parent = self.context.__parent__ #DESCRIPTOR(miano, June 2011) This originally first checked the parent's #descriptor then the item's descriptor. Why??? #This was causing an error in the display pages of items in the #workspace since the workspace containers have no descriptor #defined for them. if IAlchemistContent.providedBy(self.context): descriptor = queryModelDescriptor(self.context.__class__) elif IAlchemistContainer.providedBy(parent): descriptor = queryModelDescriptor(parent.domain_model) else: raise RuntimeError("Unsupported object: %s." % repr(self.context)) if descriptor: name = getattr(descriptor, "display_name", None) if name is None: name = self.context.__class__.__name__ return name # !+RENAME get_object_class_name def getObjectClass(self): """Get the context object's class name. Called from the view template. """ return self.context.__class__.__name__ # !+ from ui.forms.common.BaseForm -- merge these 2 base classes? @property def invariantErrors(self): """ () -> [error:zope.interface.Invalid] """ errors = [] for error in self.errors: if isinstance(error, interface.Invalid): errors.append(error) return errors @property def invariantMessages(self): """ () -> [message:str] Called from the form.html#display template. """ return filter(None, [error.message for error in self.invariantErrors])
class TimeLineViewlet(browser.BungeniItemsViewlet): """ tracker/timeline view: Chronological changes are aggregated from : bill workflow, bill audit, bill scheduling and bill event records. """ # evoque render = z3evoque.ViewTemplateFile("viewlets.html#timeline") # zpt #render = ViewPageTemplateFile("templates/timeline_viewlet.pt") # sqlalchemy give me a rough time sorting a union, # with hand coded sql it is much easier. # !+ get rid of the hard-coded sql sql_timeline = "" add_action = form.Actions( form.Action(_(u"add event"), success="handle_event_add_action"), ) view_title = _("Timeline") view_id = "unknown-timeline" changes_getter = { "atype": lambda item: item.action, "adate": lambda item: item.date_active, "description": format_change_description, "notes": lambda item: "", } events_getter = { "atype": lambda item: "event", "adate": lambda item: item.event_date, "description": lambda item: "<a href='%s'>%s</a>" \ %(url.absoluteURL(item, common.get_request()), item.short_name), "notes": lambda item: "", } def __init__(self, context, request, view, manager): super(TimeLineViewlet, self).__init__(context, request, view, manager) self.formatter = self.get_date_formatter("dateTime", "medium") def handle_event_add_action(self, action, data): self.request.response.redirect(self.addurl) def update(self): self.items = itertools.chain(*[ load_formatted_container_items(self.context.changes, self.changes_getter), load_formatted_container_items(self.context.event, self.events_getter) ]) self.items = sorted(self.items, key=lambda item: item["adate"], reverse=True) def update_sql(self): """Refresh the query. """ #!+_TIMELINE(mb, aug-2011) to deprecate this function and use # sub-container listings as shown above to acess timeline items # evaluate serialization of a dict, failure returns an empty dict def _eval_as_dict(s): try: d = eval(s) assert isinstance(d, dict) return d except (SyntaxError, TypeError, AssertionError): #debug.log_exc(sys.exc_info(), log_handler=log.info) return {} # !+CHANGE_EXTRAS(mr, dec-2010) # only *Change records have an extras dict (as "notes" str attr) and the # content of this depends on the value of "atype" (see core/audit.py) item_id = self.context.parliamentary_item_id self.items = [ dict(atype=action, item_id=piid, description=desc, adate=date, notes=_eval_as_dict(notes)) for action, piid, desc, date, notes in queries.execute_sql(self.sql_timeline, item_id=item_id) ] # Filter out workflow draft items for anonymous users if get_principal_id() in ("zope.anybody", ): _draft_states = ("draft", "working_draft") def show_timeline_item(item): if item["atype"] == "workflow": if item["notes"].get("destination") in _draft_states: return False return True self.items = [ item for item in self.items if show_timeline_item(item) ] #change_cls = getattr(domain, "%sChange" % (self.context.__class__.__name__)) for r in self.items: # workflow if r["atype"] == "workflow": # description # the workflow transition change log stores the (unlocalized) # human title for the transition's destination workflow state # -- here we just localize what is supplied: r["description"] = _(r["description"]) # NOTE: we could elaborate an entirely custom description # from scratch e.g via interpolation of a template string: ''' if r["notes"].get("destination", ""): description = "%s %s" % ( _("some text"), _(misc.get_wf_state( self.context, r["notes"]["destination"]))) ''' # event elif r["atype"] == "event": # description r["description"] = """<a href="event/obj-%s">%s</a>""" % ( r["item_id"], _(r["description"])) # version elif r["atype"] == "version": # description try: r["description"] = """<a href="versions/obj-%s">%s</a>""" % ( r["notes"]["version_id"], _(r["description"])) except (KeyError, ): # no recorded version_id, just localize what is supplied r["description"] = _(r["description"]) # path = url.absoluteURL(self.context, self.request) self.addurl = "%s/event/add" % (path)
class SecondaryNavigationViewlet(browser.BungeniViewlet): # evoque render = z3evoque.ViewTemplateFile("navigation.html#secondary", i18n_domain="bungeni.core") # zpt #render = ViewPageTemplateFile("templates/secondary-navigation.pt") def update(self): request = self.request context = self.context chain = _get_context_chain(context) length = len(chain) self.items = [] if length < 2: # there must be at least: [top-level section, application] return # container is None else: # the penultimate context is the top-level container container = chain[-2] assert container.__name__ is not None if not IReadContainer.providedBy(container): return # container has no readable content assert container is not None # add container items if length > 2: context = chain[-3] else: context = None self.add_container_menu_items(context, container) # add any menu items from zcml self.add_zcml_menu_items(container) def add_zcml_menu_items(self, container): """Add the list of ZCML menu items (if any) for this top-level container. Top-level section given by container may define a menu in ZCML with naming convention: <container_name>_navigation. """ # !+ turn this into a utility zcml_menu_name_template = "%s_navigation" try: menu_name = zcml_menu_name_template % container.__name__ menu = component.getUtility(IBrowserMenu, name=menu_name) items = menu.getMenuItems(container, self.request) except (Exception, ): debug.log_exc(sys.exc_info(), log_handler=log.debug) return [] # OK, do any necessary post-processing of each menu item local_url = url.absoluteURL(container, self.request) site_url = url.absoluteURL(getSite(), self.request) request_url = self.request.getURL() default_view_name = queryDefaultViewName(container, self.request) selection = None for item in sorted(items, key=lambda item: item['action'], reverse=True): action = item['action'] if default_view_name == action.lstrip('@@'): _url = local_url if selection is None: selected = sameProxiedObjects(container, self.context) else: _url = make_absolute(action, local_url, site_url) if selection is None: selected = pos_action_in_url(action, request_url) item['url'] = _url item['selected'] = selected and u'selected' or u'' if selected: # self is marker selection = self selected = False self.items.append(item) def add_container_menu_items(self, context, container): request = self.request # add a menu item for each user workspace, if we are in an # IWorkspaceSectionLayer # !+ if user is logged in or if request.layer_data if (interfaces.IWorkspaceSectionLayer.providedBy(request) or interfaces.IWorkspaceSchedulingSectionLayer.providedBy( request)): try: workspaces = IAnnotations(request)["layer_data"].get( "workspaces") except: workspaces = [] log.info("%s got user workspaces: %s" % (self, workspaces)) base_url_path = "/workspace" for workspace in workspaces: log.info("appending menu item for user workspace: %s" % str(workspace)) self.items.append( url.get_menu_item_descriptor( workspace.full_name, pos_action_in_url( "/workspace/obj-%s" % workspace.group_id, request.getURL()), base_url_path, "obj-%s" % workspace.group_id)) _url = url.absoluteURL(container, request) if IReadContainer.providedBy(container): #XXX should be the same in all containers ? container = proxy.removeSecurityProxy(container) for name, item in container.items(): if context is None: selected = False else: selected = url.same_path_names(context.__name__, name) item = proxy.removeSecurityProxy(item) if IDCDescriptiveProperties.providedBy(item): title = item.title else: props = IDCDescriptiveProperties(item) title = props.title # only items with valid title if title is not None: self.items.append( url.get_menu_item_descriptor(title, selected, _url, name)) default_view_name = queryDefaultViewName(container, self.request) default_view = component.queryMultiAdapter((container, self.request), name=default_view_name) if hasattr(default_view, "title") and default_view.title is not None: self.items.insert( 0, url.get_menu_item_descriptor( default_view.title, sameProxiedObjects(container, self.context), _url))
class WorkflowActionViewlet(browser.BungeniBrowserView, BaseForm, viewlet.ViewletBase): """Display workflow status and actions.""" class IWorkflowComment(zope.interface.Interface): note = zope.schema.Text(title=_("Comment on workflow change"), required=False) date_active = zope.schema.Datetime(title=_("Active Date"), required=True) @zope.interface.invariant def valid_date_active(comment): request = common.get_request() # recover min_date_active, and adjust it to be 59 secs earlier to # avoid issues of doing 2 transitions in quick succession (within # the same minute) the 2nd of which could be taken to be too old... min_date_active = (IAnnotations(request)["min_date_active"] - timedelta(seconds=59)) if not hasattr(comment, "date_active"): # !+ because of a BUG in the datetime widget (probably) : # after a server restart, resubmitting a previously loaded # form -- that displays valid data_active value results in a # form.NoDataInput("date_active") error... thus causing: # (comment.date_active<min_date_active) to be False ! raise zope.interface.Invalid(_("NoDataInput for Active Date.")) elif comment.date_active < min_date_active: raise zope.interface.Invalid(_("Active Date is too old.")) elif comment.date_active > datetime.now(): raise zope.interface.Invalid( _("Active Date is in the future.")) class WorkflowComment(object): note = u"" date_active = None form_name = "Workflow" form_fields = form.Fields(IWorkflowComment) # [form.FormField] actions = () # evoque render = z3evoque.ViewTemplateFile("form.html#form") # zpt #render = ViewPageTemplateFile("templates/viewlet.pt") def update(self, transition=None): self.adapters = { self.IWorkflowComment: self.WorkflowComment(), } wf = interfaces.IWorkflow(self.context) if transition is not None: state_transition = wf.getTransitionById(transition) self.status = _( u"Confirmation required for workflow transition: '${title}'", mapping={"title": _(state_transition.title)}) self.setupActions(transition) super(WorkflowActionViewlet, self).update() # after we transition we have different actions self.setupActions(transition) # only display the notes field to comment if there is an action # and a log table auditor = audit.getAuditor(self.context) if len(self.actions) == 0: self.form_fields = self.form_fields.omit("note", "date_active") elif auditor is None: self.form_fields = self.form_fields.omit("note", "date_active") else: # note widget note_widget = TextAreaWidget note_widget.height = 1 self.form_fields["note"].custom_widget = note_widget # date_active widget self.form_fields["date_active"].custom_widget = TextDateTimeWidget # !+ for "past data entry" mode, the default "date_active" value # should be gotten from a "pseudo_current_date" service utility self.setUpWidgets() # update form status in case of any errors # !+ follow the "bungeni descriptor schema_invariants" way of doing # this, i.e. displaying widget-specific errors next to each widget if self.errors: if self.status is None: self.status = _("Errors") self.status = "%s: %s " % ( self.status, " / ".join([ e.message #or e.__class__.__name__ for e in self.errors ])) def setupActions(self, transition): self.wf = interfaces.IWorkflowInfo(self.context) if transition is None: transitions = self.wf.getManualTransitionIds() else: transitions = (transition, ) self.actions = bindTransitions(self, transitions, None, interfaces.IWorkflow(self.context)) def setUpWidgets(self, ignore_request=False): # setup widgets in data entry mode not bound to context self.widgets = form.setUpDataWidgets(self.form_fields, self.prefix, self.context, self.request, ignore_request=ignore_request)
class MyGroupsViewlet(WorkspaceViewlet): view_id = "my_groups" view_title = _("My Groups") # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#groups") # ZPT #render = ViewPageTemplateFile("templates/workspace_group_viewlet.pt") def _get_items(self): formatter = self.get_date_formatter("date", "long") data_list = [] results = self.query.all() # if no current parliament, no data try: parliament_id = model_utils.get_current_parliament().parliament_id except: return data_list # government_id = self.__parent__.government_id for result in results: data = {} data["qid"] = "g_%s" % (result.group_id) data["subject"] = result.short_name data["title"] = "%s (%s)" % (result.short_name, result.type) data["result_item_class"] = "workflow-state-%s" % (result.status) _url = "/archive/browse/parliaments/obj-%s" % (parliament_id) if type(result) == domain.Parliament: data["url"] = url.set_url_context(_url) continue elif type(result) == domain.Committee: #data["url"] = url + "/committees/obj-" + str(result.group_id) data["url"] = url.set_url_context("/groups/%s/%s" % ( result.parent_group.group_principal_id, result.group_principal_id)) elif type(result) == domain.PoliticalGroup: data["url"] = url.set_url_context( "%s/politicalgroups/obj-%s" % (_url, result.group_id)) elif type(result) == domain.Ministry: data["url"] = url.set_url_context( "%s/governments/obj-%s/ministries/obj-%s" % ( _url, government_id, result.group_id)) else: data["url"] = "#" data["status"] = misc.get_wf_state(result) data["status_date"] = formatter.format(result.status_date) data["owner"] = "" data["type"] = _(result.type) data["to"] = "" data_list.append(data) return data_list def update(self): """refresh the query """ session = Session() #user_id = self.__parent__.user_id #parliament_id = self.__parent__.context.parliament_id group_ids = self.__parent__.user_group_ids gfilter = sql.and_(domain.Group.group_id.in_(group_ids), domain.Group.status == "active") groups = session.query(domain.Group).filter(gfilter) self.query = groups self.items = self._get_items()
class BreadCrumbsViewlet(browser.BungeniViewlet): """Breadcrumbs. Render the breadcrumbs to show a user his current location. """ # evoque render = z3evoque.ViewTemplateFile("navigation.html#breadcrumbs") # zpt #render = ViewPageTemplateFile( 'templates/breadcrumbs.pt' ) def __init__(self, context, request, view, manager): self.context = context self.request = request self.__parent__ = view self.manager = manager self.path = [] self.site_url = url.absoluteURL(getSite(), self.request) self.user_name = '' def _get_path(self, context): """Return the current path as a list """ descriptor = None name = None path = [] context = proxy.removeSecurityProxy(context) if context is None: return path # Proof-of-concept: support for selective inclusion in breadcrumb trail: # a view marked with an attribute __crumb__=False is NOT included in # the breadcrumb trail (see core/app.py: "workspace" Section) if not getattr(context, "__crumb__", True): return path if context.__parent__ is not None: path.extend(self._get_path(context.__parent__)) _url = url.absoluteURL(context, self.request) # Append a trailing slash to each breadcrumb entry so that # the right context is always maintained when the breadcrumbs # are used for navigation. _url = url.set_url_context(_url) title = _get_title_from_context(context) if title is not None: path.append({'name': title, 'url': _url}) return path def update(self): self.path = self._get_path(self.context) # if the view is a location, append this to the breadcrumbs if ILocation.providedBy(self.__parent__) and \ IDCDescriptiveProperties.providedBy(self.__parent__): self.path.append({ 'name': self.__parent__.title, 'url': None, }) try: self.user_name = self.request.principal.login except: pass
class DraftSittingsViewlet(WorkspaceViewlet): """The "agendas/minutes" tab in the workspace/pi view for the Clerk. """ # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#sittings") # zpt #render = ViewPageTemplateFile("templates/workspace_sitting_viewlet.pt") view_id = "sitting-draft" view_title = _("agendas/minutes") states = get_states("groupsitting", tagged=["workspace"]) def _get_items(self): data_list = [] results = self.query.all() formatter = self.get_date_formatter("date", "long") time_formatter = self.get_date_formatter("time", "short") for result in results: data = {} data["subject"] = result.short_name # this tab appears in the workspace pi/ view... data["url"] = url.set_url_context( "../../scheduling/sittings/obj-%i/schedule" % result.group_sitting_id) # Note: same UI is also displayed at: # /business/sittings/obj-%i/schedule % result.group_sitting_id data["items"] = "" data["status"] = misc.get_wf_state(result) data["status_date"] = formatter.format(result.status_date) data["owner"] = "" data["type"] = result.group.type data["group"] = u"%s %s" % ( result.group.type.capitalize(), result.group.short_name) data["time_from_to"] = ( time_formatter.format(result.start_date), time_formatter.format(result.end_date)) data["date"] = formatter.format(result.start_date) if result.venue: data["venue"] = _(result.venue.short_name) #else: # date["venue"] = "" if type(result) == domain.Question: data["to"] = result.ministry.short_name else: data["to"] = "" # past, present, future today = datetime.datetime.today().date() startday = result.start_date.date() if today == startday: data["css_class"] = "present" elif today > startday: data["css_class"] = "past" else: data["css_class"] = "future" data_list.append(data) return data_list def update(self): """Refresh the query """ session = Session() qfilter = domain.GroupSitting.status.in_(self.states) sittings = session.query(domain.GroupSitting).filter( qfilter).order_by(domain.GroupSitting.start_date.desc() ).options( eagerload("group") ) self.query = sittings self.items = self._get_items()
class TimeLineViewlet(viewlet.ViewletBase): """ tracker/timeline view: Chronological changes are aggregated from : bill workflow, bill audit, bill scheduling and bill event records. """ # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#timeline") # zpt #render = ViewPageTemplateFile('templates/timeline_viewlet.pt') # sqlalchemy give me a rough time sorting a union, # with hand coded sql it is much easier. # !+ get rid of the hard-coded sql sql_timeline = "" add_action = form.Actions( form.Action(_(u'add event'), success='handle_event_add_action'), ) for_display = True view_name = "Timeline" view_id = "unknown-timeline" def __init__(self, context, request, view, manager): self.context = context self.request = request self.__parent__ = view self.manager = manager self.query = None self.formatter = date.getLocaleFormatter(self.request, "dateTime", "medium") def handle_event_add_action(self, action, data): self.request.response.redirect(self.addurl) def update(self): """Refresh the query. """ # evaluate serialization of a dict, failure returns an empty dict def _eval_as_dict(s): try: d = eval(s) assert isinstance(d, dict) return d except (SyntaxError, TypeError, AssertionError): #debug.log_exc(sys.exc_info(), log_handler=log.info) return {} # NOTE: only *Change records have a "notes" dict attribute and the # content of this depends on the value of "atype" (see core/audit.py) item_id = self.context.parliamentary_item_id self.results = [ dict(atype=action, item_id=piid, description=desc, adate=date, notes=_eval_as_dict(notes)) for action, piid, desc, date, notes in queries.execute_sql(self.sql_timeline, item_id=item_id) ] # Filter out workflow draft items for anonymous users if get_principal_id() in ("zope.anybody", ): _draft_states = ("draft", "working_draft") def show_timeline_item(result): if result["atype"] == "workflow": if result["notes"].get("destination") in _draft_states: return False return True self.results = [ result for result in self.results if show_timeline_item(result) ] #change_cls = getattr(domain, "%sChange" % (self.context.__class__.__name__)) for r in self.results: # workflow if r["atype"] == "workflow": # description # the workflow transition change log stores the (unlocalized) # human title for the transition's destination workflow state # -- here we just localize what is supplied: r["description"] = _(r["description"]) # NOTE: we could elaborate an entirely custom description # from scratch e.g via interpolation of a template string: ''' if r["notes"].get("destination", ""): description = "%s %s" % ( _("some text"), _(misc.get_wf_state( self.context, r["notes"]["destination"]))) ''' # event elif r["atype"] == "event": # description r["description"] = """<a href="event/obj-%s">%s</a>""" % ( r["item_id"], _(r["description"])) # version elif r["atype"] == "version": # description try: r["description"] = """<a href="versions/obj-%s">%s</a>""" % ( r["notes"]["version_id"], _(r["description"])) except (KeyError, ): # no recorded version_id, just localize what is supplied r["description"] = _(r["description"]) # path = url.absoluteURL(self.context, self.request) self.addurl = '%s/event/add' % (path)
class WorkspaceViewlet(browser.BungeniItemsViewlet): # evoque render = z3evoque.ViewTemplateFile("workspace_viewlets.html#items")