def create_subtask_response(context, event): """When adding a new task object within a task(subtask), it adds a response to the maintask. """ # the event is fired multiple times when the task was transported, so we # need to verify that the request was not called by another client. request = context.REQUEST if request.get_header('X-OGDS-AC', None) or \ request.get_header('X-OGDS-AUID', None) or \ request.get('X-CREATING-SUCCESSOR', None): return parent = aq_parent(aq_inner(context)) if ITask.providedBy(parent): if ITask.providedBy(context): transition = 'transition-add-subtask' # If the the added object is a subtask we have to make sure # that the subtask is already synced to the globalindex if not context.get_sql_object(): TaskSqlSyncer(context, event).sync() elif IBaseDocument.providedBy(context): transition = 'transition-add-document' # add a response with a link to the object add_simple_response(parent, added_object=context, transition=transition)
def get_containing_task(self): """ Get the parent-tasks if we have one """ parent = aq_parent(aq_inner(self.context)) if ITask.providedBy(parent): return [parent] return []
def get_containing_task(self): """ Get the parent-tasks if we have one """ parent = aq_parent(aq_inner(self.context)) if ITask.providedBy(parent): return parent.get_sql_object() return None
def get_added_objects(self): """ Returns two lists of docs, subtasks """ try: self.added_object except AttributeError: return [], [] # .. and sometimes it may be empty. if not self.added_object: return [], [] # Support for multiple added objects if hasattr(self.added_object, '__iter__'): relations = self.added_object else: relations = [self.added_object] docs = [] subtasks = [] for rel in relations: obj = rel.to_object if ITask.providedBy(obj): subtasks.append(obj) else: docs.append(obj) return docs, subtasks
def createAndAdd(self, data): created = [] # make sure we don't create private tasks when the feature is # not enabled. the field is hidden, but users could still submit. if not is_private_task_feature_enabled(): data['is_private'] = False if isinstance(data['responsible'], basestring): data['responsible'] = [data['responsible']] all_responsible_users = data['responsible'] for responsible in all_responsible_users: data['responsible'] = responsible update_reponsible_field_data(data) created.append(self._create_task(data)) # Restore responsible in data data['responsible'] = all_responsible_users # Set tasktemplate order and move to planned state if it's part # of a sequential process if ITask.providedBy( self.context) and self.context.is_sequential_main_task(): position = data['tasktemplate_position'] if not position: position = len(self.context.get_tasktemplate_order()) for task in created: self.context.add_task_to_tasktemplate_order(position, task) self._set_immediate_view(created) return created
def _set_immediate_view(self, created): """The default behavior implemented by the dexterity add form is circumvented by this form. If there's only one task the immediate_view of the task fti is respected. If there is more than one task, a differen TaskRedirector implementation is used.""" if len(created) == 1: task = created[0] fti = getUtility(IDexterityFTI, name=self.portal_type) if fti.immediate_view: self.immediate_view = "{0}/{1}/{2}".format( self.context.absolute_url(), task.id, fti.immediate_view, ) else: self.immediate_view = "{0}/{1}".format( self.context.absolute_url(), task.id) else: if ITask.providedBy(self.context): redirect_to = '{0}#overview'.format( self.context.absolute_url()) else: redirect_to = '{0}#tasks'.format(self.context.absolute_url()) self.immediate_view = redirect_to
def get_redirect_url(context): """return the url where the editing_document view was called from It should be a document listing.""" referer = context.REQUEST.environ.get('HTTP_REFERER') portal_url = '/'.join(context.portal_url().split('/')[:-1]) if referer: obj_path = referer[len(portal_url):] try: obj = context.restrictedTraverse(obj_path) except KeyError: return '%s#overview' % context.absolute_url() # redirect to right tabbedview-tab if ITask.providedBy(obj): return '%s#relateddocuments' % (obj.absolute_url()) elif IPloneSiteRoot.providedBy(obj): return '%s#mydocuments' % (obj.absolute_url()) elif IDossierMarker.providedBy(obj): return '%s#documents' % (obj.absolute_url()) else: return obj.absolute_url() else: return '%s#overview' % context.absolute_url()
def render(self): parent = aq_inner(aq_parent(self.context)) if ITask.providedBy(parent): redirect_to = '%s#overview' % parent.absolute_url() else: redirect_to = '%s#tasks' % parent.absolute_url() return self.request.RESPONSE.redirect(redirect_to)
def reindex_dossier_and_children(self, dossier): children = self.catalog(path='/'.join(dossier.getPhysicalPath())) for child in children: obj = child.getObject() obj.reindexObject(idxs=['reference']) if ITask.providedBy(obj): obj.get_sql_object().sync_with(obj)
def reindex_containing_dossier_for_contained_objects(dossier, event): """Reindex the containging_dossier index for all the contained obects. """ for brain in dossier.portal_catalog(path='/'.join(dossier.getPhysicalPath())): obj = brain.getObject() obj.reindexObject(idxs=['containing_dossier']) if ITask.providedBy(obj): sync_task(brain.getObject(), event)
def object_moved_or_added(context, event): if isinstance(event, ObjectAddedEvent): # Don't consider moving or removing an object a "touch". Mass-moves # would immediately fill up the touched log, and removals should not # be tracked anyway. if should_track_touches(context): notify(ObjectTouchedEvent(context)) # If an object has been copy & pasted, we need to reindex some fields. # The IObjectCopiedEvent is too early to do that though, because at # that point the object doesn't have a full AQ chain yet. We therefore # just mark it during IObjectCopiedEvent, and do the reindexing here. if getattr(context, '_v_object_has_been_copied', False): context.reindexObject(idxs=reindex_after_copy) if IObjectRemovedEvent.providedBy(event): return # Update object security after moving or copying. # Specifically, this is needed for the case where an object is moved out # of a location where a Placeful Workflow Policy applies to a location # where it doesn't. # # Plone then no longer provides the placeful workflow for that object, # but doesn't automatically update the object's security. # # We use ftw.upgrade's update_security_for() here to correctly # recalculate security, but do the reindexing ourselves because otherwise # Plone will do it recursively (unnecessarily so). changed = update_security_for(context, reindex_security=False) if changed: reindex_object_security_without_children(context) # There are several indices that need updating when a dossier is moved. # first make sure obj was actually moved and not created if not event.oldParent or not event.newParent: return # When an object is moved, its containing_dossier needs reindexing. to_reindex = ['containing_dossier'] # containing_subdossier is really only used for documents, # while is_subdossier is only meaningful for dossiers. if IDossierMarker.providedBy(context): was_subdossier = IDossierMarker.providedBy(event.oldParent) is_subdossier = IDossierMarker.providedBy(event.newParent) if was_subdossier != is_subdossier: to_reindex.append('is_subdossier') if context.portal_type in TYPES_WITH_CONTAINING_SUBDOSSIER_INDEX: to_reindex.append('containing_subdossier') context.reindexObject(idxs=to_reindex) # synchronize with model if necessary if ITask.providedBy(context): sync_task(context, event)
def reindex_containing_dossier_for_contained_objects(dossier, event): """Reindex the containging_dossier index for all the contained obects. """ for brain in dossier.portal_catalog( path='/'.join(dossier.getPhysicalPath())): obj = brain.getObject() obj.reindexObject(idxs=['containing_dossier']) if ITask.providedBy(obj): sync_task(brain.getObject(), event)
def update_deadline(task, event): """Update the parents deadline, when a subtask deadline has changed.""" for descr in event.descriptions: for attr in descr.attributes: if attr == 'deadline': parent = aq_parent(aq_inner(task)) if ITask.providedBy(parent): temp = task.deadline + timedelta(MAIN_TASK_DEADLINE_DELTA) if parent.deadline < temp: parent.deadline = temp parent.reindexObject()
def get_type(self, item): """differ the object typ and return the type as string """ if not item: return None elif isinstance(item, dict): return 'dict' elif isinstance(item, Task) or ITask.providedBy(item): return 'task' else: return 'obj'
def get_type(self, item): """differ the object typ and return the type as string """ if not item: return None elif isinstance(item, dict): return "dict" elif isinstance(item, Task) or ITask.providedBy(item): return "task" else: return "obj"
def set_initial_state(task, event): """When adding a subtask to a sequential task process, the new task should be in the planned state. """ if IDuringTaskTemplateFolderTriggering.providedBy(getRequest()): return parent = aq_parent(aq_inner(task)) if ITask.providedBy(parent) \ and parent.is_sequential_main_task(): task.set_to_planned_state()
def _get_task_css_class(task): """A task helper function for `get_css_class`, providing some metadata of a task. The task may be a brain, a dexterity object or a sql alchemy globalindex object. """ if ICatalogBrain.providedBy(task) or ISolrDocument.providedBy(task): task = Task.query.by_brain(task) if ITask.providedBy(task): task = task.get_sql_object() return task.get_css_class()
def set_initial_state(task, event): """When adding a subtask to a sequential task process, the new task should be in the planned state. """ if IDuringTaskTemplateFolderTriggering.providedBy(getRequest()): return parent = aq_parent(aq_inner(task)) if ITask.providedBy(parent) \ and IFromSequentialTasktemplate.providedBy(parent): task.set_to_planned_state()
def _task_state_wrapper(self, item, text): """ Wrap a span-tag around the text with the status-css class """ if isinstance(item, Task): # Its a sql-task-object state = item.review_state elif ITask.providedBy(item): # Its a task-object state = getToolByName(self.context, "portal_workflow").getInfoFor(item, "review_state") else: return "" return '<span class="wf-%s">%s</span>' % (state, text)
def render_task(self, item): """ Render the taskobject """ if isinstance(item, Task): # Its a task stored in sql return self._sqlalchemy_task_link(item) elif ITask.providedBy(item): # Its a normal task object return self._object_task_link(item) else: return None
def update_deadline(task, event): """Update the parents deadline, when a subtask deadline has changed.""" for descr in event.descriptions: for attr in descr.attributes: if attr == 'deadline': parent = aq_parent(aq_inner(task)) if ITask.providedBy(parent): offset = api.portal.get_registry_record( 'deadline_timedelta', interface=ITaskSettings) temp = task.deadline + timedelta(offset) if parent.deadline < temp: parent.deadline = temp parent.reindexObject()
def _task_state_wrapper(self, item, text): """ Wrap a span-tag around the text with the status-css class """ if isinstance(item, Task): # Its a sql-task-object state = item.review_state elif ITask.providedBy(item): # Its a task-object state = getToolByName(self.context, 'portal_workflow').getInfoFor( item, 'review_state') else: return '' return '<span class="wf-%s">%s</span>' % (state, text)
def is_available(self): """The action should only be available on closed tasks for administrators and task issuer and only if the feature is enabled. """ if not is_optional_task_permissions_revoking_enabled(): return False if not ITask.providedBy(self.context): return False if not self.context.get_review_state() in FINAL_TASK_STATES: return False if api.user.has_permission('cmf.ManagePortal'): return True issuer = self.context.get_issuer_actor() return issuer.identifier == api.user.get_current().id
def object_moved_or_added(context, event): if isinstance(event, ObjectAddedEvent): # Don't consider moving or removing an object a "touch". Mass-moves # would immediately fill up the touched log, and removals should not # be tracked anyway. if should_track_touches(context): notify(ObjectTouchedEvent(context)) if IObjectRemovedEvent.providedBy(event): return # Update object security after moving or copying. # Specifically, this is needed for the case where an object is moved out # of a location where a Placeful Workflow Policy applies to a location # where it doesn't. # # Plone then no longer provides the placeful workflow for that object, # but doesn't automatically update the object's security. # # We use ftw.upgrade's update_security_for() here to correctly # recalculate security, but do the reindexing ourselves because otherwise # Plone will do it recursively (unnecessarily so). changed = update_security_for(context, reindex_security=False) if changed: reindex_object_security_without_children(context) # There are several indices that need updating when a dossier is moved. # first make sure obj was actually moved and not created if not event.oldParent or not event.newParent: return # When an object is moved, its containing_dossier needs reindexing. to_reindex = ['containing_dossier'] # containing_subdossier is really only used for documents, # while is_subdossier is only meaningful for dossiers. if IDossierMarker.providedBy(context): was_subdossier = IDossierMarker.providedBy(event.oldParent) is_subdossier = IDossierMarker.providedBy(event.newParent) if was_subdossier != is_subdossier: to_reindex.append('is_subdossier') if context.portal_type in TYPES_WITH_CONTAINING_SUBDOSSIER_INDEX: to_reindex.append('containing_subdossier') context.reindexObject(idxs=to_reindex) # synchronize with model if necessary if ITask.providedBy(context): sync_task(context, event)
def fix_child_mappings(self): dossier_brains = self.catalog(object_provides=IDossierMarker.__identifier__) for brain in dossier_brains: obj = brain.getObject() if ITemplateDossier.providedBy(obj): continue if obj.id in self.ignored_ids: continue self._fix_wrong_mappings(obj) for obj in self.objs_to_reindex: obj.reindexObject(idxs=['reference']) if ITask.providedBy(obj): obj.get_sql_object().sync_with(obj)
def get_parent_dossier(self): """Return the document's parent dossier. A parent dossier is available for documents in a dossier/subdossier or for documents in a task. No parent dossier is available for documents in an inbox, in a forwarding or inside a proposal. In that case this method returns None. """ parent = aq_parent(aq_inner(self)) if IDossierMarker.providedBy(parent) or IDossierTemplateMarker.providedBy(parent): return parent if ITask.providedBy(parent): return parent.get_containing_dossier() return None
def create_subtask_response(context, event): """When adding a new task object within a task(subtask), it adds a response to the maintask. """ # the event is fired multiple times when the task was transported, so we # need to verify that the request was not called by another client. request = context.REQUEST if request.get_header('X-OGDS-AC', None) or \ request.get_header('X-OGDS-CID', None) or \ request.get('X-CREATING-SUCCESSOR', None): return parent = aq_parent(aq_inner(context)) if ITask.providedBy(parent): # add a response with a link to the object add_simple_response(parent, added_object=context)
def add_role_assignments(self, proposal): document = proposal.get_proposal_document() # should not be possible to happen, but proposals without document # cannot be referenced by tasks thus no need to worry about that in # this upgrade. if not document: return relation_catalog = getUtility(ICatalog) intids = getUtility(IIntIds) query = {'to_id': intids.getId(document)} relations = relation_catalog.findRelations(query) for relation in relations: if ITask.providedBy(relation.from_object): task = relation.from_object self.add_role_assignment_to_proposal(document, task)
def test_create_a_task_for_every_selected_person_with_multiple_orgunits(self, browser): client2 = create(Builder('org_unit') .with_default_groups() .id('client2') .having(title='Client2', admin_unit=self.admin_unit)) user = create(Builder('ogds_user') .assign_to_org_units([client2]) .having(userid='some.user')) dossier = create(Builder('dossier')) doc = create(Builder("document").titled("test-doc") .within(dossier)) responsible_users = [ self.get_org_unit().id() + ':' + TEST_USER_ID, client2.id() + ':' + user.userid ] browser.login().visit(dossier) factoriesmenu.add('Task') browser.fill({'Title': 'Task title', 'Task Type': 'To comment', 'Related Items': doc}) form = browser.find_form_by_field('Responsible') form.find_widget('Responsible').fill(responsible_users) browser.find('Save').click() tasks = filter(lambda item: ITask.providedBy(item), dossier.objectValues()) self.assertEquals(2, len(tasks), 'Expect 2 tasks') self.assertEquals(TEST_USER_ID, tasks[0].responsible) self.assertEquals('client1', tasks[0].responsible_client) self.assertEquals(user.userid, tasks[1].responsible) self.assertEquals('client2', tasks[1].responsible_client) activities = Activity.query.all() self.assertEquals(2, len(activities)) self.assertEquals(u'task-added', activities[0].kind) self.assertEquals(TEST_USER_ID, activities[0].actor_id) self.assertEquals(u'task-added', activities[1].kind) self.assertEquals(TEST_USER_ID, activities[1].actor_id)
def set_workflow_state(self, new_workflow_state_id, *objects): """Set the workflow state of one or many objects. When the state is changed for multiple nested objects at once, the method can optimize reindexing security so that it is not executed multiple times for the same object. """ wftool = api.portal.get_tool('portal_workflow') for obj in objects: chain = wftool.getChainFor(obj) self.assertEquals( 1, len(chain), 'set_workflow_state does only support objects with' ' exactly one workflow, but {!r} has {!r}.'.format(obj, chain)) workflow = wftool[chain[0]] self.assertIn(new_workflow_state_id, workflow.states) wftool.setStatusOf(chain[0], obj, { 'review_state': new_workflow_state_id, 'action': '', 'actor': '' }) workflow.updateRoleMappingsFor(obj) obj.reindexObject(idxs=['review_state']) if ITask.providedBy(obj): obj.get_sql_object().review_state = new_workflow_state_id # reindexObjectSecurity is recursive. We should avoid updating the same # object twice when the parent is changed too. security_reindexed = [] for obj in sorted(objects, key=methodcaller('getPhysicalPath')): current_path = '/'.join(obj.getPhysicalPath()) if any( filter(lambda path: current_path.startswith(path), security_reindexed)): # We just have updated the security of a parent recursively, # thus the security of ``obj`` must be up to date at this point. break obj.reindexObjectSecurity() security_reindexed.append(current_path)
def get_parent_dossier(self): """Return the document's parent dossier. A parent dossier is available for documents in a dossier/subdossier or for documents in a task. No parent dossier is available for documents in an inbox, in a forwarding or inside a proposal. In that case this method returns None. """ parent = aq_parent(aq_inner(self)) if (IDossierMarker.providedBy(parent) or IDossierTemplateMarker.providedBy(parent)): return parent if ITask.providedBy(parent): return parent.get_containing_dossier() if IProposal.providedBy(parent): return parent.get_containing_dossier() return None
def get_task_info(self, item): """ return some task attributes for objects: Responsible client ID / responsible """ if not ITask.providedBy(item): return "" info = getUtility(IContactInformation) if not item.responsible_client or len(info.get_clients()) <= 1: # No responsible client is set yet or we have a single client # setup. return info.describe(item.responsible) # Client client = client_title_helper(item, item.responsible_client) taskinfo = "%s / %s" % (client, info.render_link(item.responsible)) return taskinfo.encode("utf-8")
def set_workflow_state(self, new_workflow_state_id, *objects): """Set the workflow state of one or many objects. When the state is changed for multiple nested objects at once, the method can optimize reindexing security so that it is not executed multiple times for the same object. """ wftool = api.portal.get_tool('portal_workflow') for obj in objects: chain = wftool.getChainFor(obj) self.assertEquals( 1, len(chain), 'set_workflow_state does only support objects with' ' exactly one workflow, but {!r} has {!r}.'.format(obj, chain)) workflow = wftool[chain[0]] self.assertIn(new_workflow_state_id, workflow.states) wftool.setStatusOf(chain[0], obj, { 'review_state': new_workflow_state_id, 'action': '', 'actor': ''}) workflow.updateRoleMappingsFor(obj) obj.reindexObject(idxs=['review_state']) if ITask.providedBy(obj): obj.get_sql_object().review_state = new_workflow_state_id # reindexObjectSecurity is recursive. We should avoid updating the same # object twice when the parent is changed too. security_reindexed = [] for obj in sorted(objects, key=methodcaller('getPhysicalPath')): current_path = '/'.join(obj.getPhysicalPath()) if any(filter(lambda path: current_path.startswith(path), security_reindexed)): # We just have updated the security of a parent recursively, # thus the security of ``obj`` must be up to date at this point. break obj.reindexObjectSecurity() security_reindexed.append(current_path)
def get_task_info(self, item): """ return some task attributes for objects: Responsible client ID / responsible """ if not ITask.providedBy(item): return '' info = getUtility(IContactInformation) if not item.responsible_client or len(info.get_clients()) <= 1: # No responsible client is set yet or we have a single client # setup. return info.describe(item.responsible) # Client client = client_title_helper(item, item.responsible_client) taskinfo = "%s / %s" % ( client, info.render_link(item.responsible), ) return taskinfo.encode('utf-8')
def _set_immediate_view(self, created): """The default behavior implemented by the dexterity add form is circumvented by this form. If there's only one task the immediate_view of the task fti is respected. If there is more than one task, a differen TaskRedirector implementation is used.""" if len(created) == 1: task = created[0] fti = getUtility(IDexterityFTI, name=self.portal_type) if fti.immediate_view: self.immediate_view = "{0}/{1}/{2}".format( self.context.absolute_url(), task.id, fti.immediate_view,) else: self.immediate_view = "{0}/{1}".format( self.context.absolute_url(), task.id) else: if ITask.providedBy(self.context): redirect_to = '{0}#overview'.format(self.context.absolute_url()) else: redirect_to = '{0}#tasks'.format(self.context.absolute_url()) self.immediate_view = redirect_to
def is_subtask(obj): """ is_subtask indexer """ parent = aq_parent(aq_inner(obj)) return ITask.providedBy(parent)
def test_adding(self): t1 = create_task(self.portal, title='Task 1') self.failUnless(ITask.providedBy(t1))
def is_inside_a_task(self): parent = aq_parent(aq_inner(self)) return ITask.providedBy(parent)
def progress_to_cancelled_guard(self, c, include_agency): """Checks if: The task is generated from tasktemplate and its the main task. """ return self.context.is_from_tasktemplate and not \ ITask.providedBy(aq_parent(self.context))
def test_factory(self): fti = queryUtility(IDexterityFTI, name='opengever.task.task') factory = fti.factory new_object = createObject(factory) self.failUnless(ITask.providedBy(new_object))
def test_adding(self): t1 = create(Builder('task').titled('Task 1')) self.failUnless(ITask.providedBy(t1))