class HumanTask(AbstractTask): with param_group('Assigned'): candidats = ComputedParam().ui(editor='choices_for', editor_options={'target_param':'user'}) user = CaseParam().ui(editor='node_ref', editor_options={'root_name':'resources'}) _status_manager = Child(HumanTaskStatus).affects(AbstractTask.status) def _configure(self): super(HumanTask, self)._configure() self.candidats.add_source(self.parent().user_groups) def compute(self, param_name): if param_name == 'candidats': if not self.candidats.has_source(): self.candidats.set([]) else: uids = self.candidats.get_from_sources() if uids is None: self.candidats.set([]) else: nodes = [ self.flow().get(uid) for uid in uids ] groups = [ n for n in nodes if n.has_param('users') ] users = [ n.uid() for n in nodes if n.has_param('login') ] [ users.extend(group.users.get() or []) for group in groups ] self.candidats.set(sorted(set(users))) else: super(HumanTask, self).compute(param_name)
class AbstractTask(FileNode): ICON_NAME = 'task' with param_group('Task'): status = CaseParam('NYS') status_choices = ComputedParam().ui(editor='choices_for', editor_options={'target_param':'status'}) progress = ComputedParam().ui(editor='percent') up_to_date = ComputedParam().ui('Is up to date?', editor='bool') #_status_manager = Child(SpecificStatusManager).affects(status) subclasses must define this! def param_touched(self, param_name): if param_name in ('status'): self.status_choices.touch() self.progress.touch() return super(AbstractTask, self).param_touched(param_name) def _is_up_to_date(self): prev_mtime = self.prev_mtime.get() if prev_mtime is None: return self.exists.get() else: mtime = self.mtime.get() if not mtime: return False else: delta = 60 # allow 60 second of delay. return self.mtime.get()+delta>self.prev_mtime.get() def compute(self, param_name): if param_name == 'progress': self.progress.set( self._status_manager.get_progress(self.status.get()) ) elif param_name == 'status_choices': self.status_choices.set( self._status_manager.get_choices(self.status.get()) ) elif param_name == 'up_to_date': self.up_to_date.set(self._is_up_to_date()) else: super(AbstractTask, self).compute(param_name)
class NamedNode(Node): with param_group('_Naming', group_index=1000): _naming = Param() _namer = Child(NamingNode) def set_namer_config(self, **config): self._namer.config.set(config) def add_namer_config(self, **config): cfg = self._namer.config.get() or {} cfg.update(config) self._namer.config.set(cfg) def set_namer_from_id(self, key=None): key = key or self.__class__.__name__ self._namer.config.set({key:self.node_id})
class TaskGroup(NamedNode): ICON_NAME = 'action' with param_group('Tasks'): tasks_statuses = Param({}, sources_as_dict=True).ui(editor='status_sumary') tasks_progresses = Param({}, sources_as_dict=True) status = ComputedParam() progress = ComputedParam().ui(editor='percent') _status_manager = Child(TaskGroupStatus) # NB: this is a status summary, it does not affect a param notes = Child(NoteThread) def _configure(self): super(TaskGroup, self)._configure() for _, child in self.iterchildren(): if child.has_param('status'): self.tasks_statuses.add_source(child.status) if child.has_param('progress'): self.tasks_progresses.add_source(child.progress) def param_touched(self, param_name): if param_name == 'tasks_progresses': self.progress.touch() elif param_name == 'progress': self.status.touch() def compute(self, param_name): if param_name == 'progress': self.progress.set( self._status_manager.get_average_progress( (self.tasks_progresses.get() or {}).values() ) ) if param_name == 'status': self.status.set( self._status_manager.get_status( self.progress.get() ) )
class Casting(Cast): ''' A Casting node is used to build up a set of interesting nodes. It does so by storing a list of Cast node uids and generating a list of nodes. Each Cast node referenced knows in which Casting it is used and provide the node it casts. As a Casting node is also a Cast node, one can cast a casting in another one. This gives the ability to build complexe and hierarchical node groups. ''' with param_group('Casting', 0): casted_uids = CaseParam().ui(editor='node_refs') casted = ComputedParam() clean_up = TriggerParam() def _configure(self): super(Casting, self)._configure() self._last_strict_casted = None def get_casted(self): return self.casted.get() def add_cast(self, cast): cast_uid = cast.uid() casted_uids = self.casted_uids.get() or [] if cast_uid not in casted_uids: self.casted_uids.set(casted_uids + [cast_uid]) def remove_cast(self, cast): cast_uid = cast.uid() casted_uids = self.casted_uids.get() or [] try: casted_uids.remove(cast_uid) except ValueError: pass else: self.casted_uids.set(casted_uids) def param_touched(self, param_name): if param_name == 'casted_uids': self.casted.touch() else: super(Casting, self).param_touched(param_name) def compute(self, param_name): if param_name == 'casted': flow = self.flow() casted = [] strict_casted = [] for uid in self.casted_uids.get() or []: node = flow.get(uid) strict_casted.append(node) try: casted.extend(node.get_casted()) except AttributeError: raise ValueError( 'The uid %r is not castable. Please add only Cast and Casting nodes' % (uid, )) # Removes disconnected: for n in self._last_strict_casted or []: if n not in strict_casted: n.remove_casting(self) # Add new connected: for n in strict_casted: if n not in (self._last_strict_casted or []): n.add_casting(self) self.casted.set(casted) self._last_strict_casted = strict_casted # Touch the casting using me: castings = self.castings.get() print '##################', castings for casting in castings: casting.casted.touch() else: super(Casting, self).compute(param_name) def trigger(self, param_name): if param_name == 'clean_up': flow = self.flow() valid_uids = [] for uid in self.casted_uids.get() or []: node = flow.get(uid) try: node.get_casted except AttributeError: continue else: valid_uids.append(uid) self.casted_uids.set(sorted(valid_uids)) else: super(Cast, self).trigger(param_name)
class Cast(Node): ''' A Cast node is used by the Casting nodes to build up a set of interesting nodes. The Cast node must be a Child of the interesting node. ''' with param_group('Casted By'): casting_uids = CaseParam().ui(editor='node_refs') castings = ComputedParam() def _configure(self): super(Cast, self)._configure() self._last_castings = None def get_casted(self): return [self.parent()] def add_casting(self, casting): casting_uid = casting.uid() casting_uids = self.casting_uids.get() or [] if casting_uid not in casting_uids: self.casting_uids.set(casting_uids + [casting_uid]) def remove_casting(self, casting): casting_uid = casting.uid() casting_uids = self.casting_uids.get() or [] try: casting_uids.remove(casting_uid) except ValueError: pass else: self.casting_uids.set(casting_uids) def param_touched(self, param_name): if param_name == 'casting_uids': self.castings.touch() else: super(Cast, self).param_touched(param_name) def compute(self, param_name): if param_name == 'castings': flow = self.flow() castings = [] for uid in self.casting_uids.get() or []: try: n = flow.get(uid) except: continue castings.append(n) # Removes disconnected: for n in self._last_castings or []: if n not in castings: n.remove_cast(self) # Add new connected: for n in castings: if n not in (self._last_castings or []): n.add_cast(self) self.castings.set(castings) self._last_castings = list(castings) else: super(Cast, self).compute(param_name)
class Scheduled(TaskGroup): ICON_NAME = 'scheduled' AFTER_PREV_END = 'After Previous End' AFTER_PREV_START = 'After Previous Start' FIXED_START = 'Fixed Start Date' SCHEDULE_MODES = ( AFTER_PREV_END, AFTER_PREV_START, FIXED_START, ) with param_group('Planning'): schedule_mode = CaseParam().ui( editor='choice', editor_options={'choices': SCHEDULE_MODES}) fixed_start_date = CaseParam().ui(editor='date') start_offset = CaseParam().ui(editor='int') start_date = ComputedParam().ui(editor='date') work_days = CaseParam().ui(editor='int') end_date = ComputedParam().ui(editor='date') with param_group('Planning Dependencies'): dep_start_dates = Param().ui(editor='date') dep_end_dates = Param().ui(editor='date') with param_group('Assigned'): manager = CaseParam().ui(editor='node_ref', editor_options={'root_name': 'resources'}) lead = CaseParam().ui(editor='node_ref', editor_options={'root_name': 'resources'}) user_groups = CaseParam().ui(editor='node_refs', editor_options={'root_name': 'resources'}) def add_dependency(self, scheduled): self.dep_start_dates.add_source(scheduled.start_date) self.dep_end_dates.add_source(scheduled.end_date) def param_touched(self, param_name): if param_name in ('schedule_mode', 'fixed_start_date', 'start_offset', 'dep_start_dates', 'dep_end_dates'): self.start_date.touch() elif param_name in ('start_date', 'work_days'): self.end_date.touch() else: return super(Scheduled, self).param_touched(param_name) def compute(self, param_name): if param_name == 'start_date': mode = self.schedule_mode.get() if mode == self.FIXED_START: self.start_date.set( self.fixed_start_date.get() + datetime.timedelta(self.start_offset.get() or 0)) elif not mode or mode == self.AFTER_PREV_END: prev_end = self.dep_end_dates.get() if prev_end is None: raise Exception( 'Cannot use schedule mode %r without a dependency.' % (mode, )) self.start_date.set( prev_end + datetime.timedelta(self.start_offset.get() or 0)) elif mode == self.AFTER_PREV_START: prev_start = self.dep_start_dates.get() if prev_start is None: raise Exception( 'Cannot use schedule mode %r without a dependency.' % (mode, )) self.start_date.set( prev_start + datetime.timedelta(self.start_offset.get() or 0)) else: raise Exception('Unknown Schedule Mode %r' % (mode, )) elif param_name == 'end_date': start = self.start_date.get() if start is None: raise Exception('Cannot find end date without start date') self.end_date.set(start + datetime.timedelta(self.work_days.get() or 0)) else: return super(Scheduled, self).compute(param_name)
class FileNode(NamedNode): thumbnail = ComputedParam().tag('preview').ui(editor='thumbnail', group_index=-1, index=-1) with param_group('Previous File'): prev_filename = Param().tag('filename') prev_mtime = Param().ui(editor='timestamp') with param_group('File'): filename = ComputedParam().tag('filename') exists = ComputedParam() mtime = ComputedParam().ui(editor='timestamp') def set_prev_file(self, file_node): self.prev_filename.disconnect() self.prev_filename.add_source(file_node.filename) self.prev_mtime.disconnect() self.prev_mtime.add_source(file_node.mtime) browse = Proc(BrowseCmdNode) def _configure(self): super(FileNode, self)._configure() self.browse.filename.add_source(self.filename) self.set_ticked() def tick(self): ''' Checks for mtime modifications and update it if needed. ''' # Skip tick when it might come from a touch of the params used here: # (it could lead to a loop) if self.exists.is_dirty() or self.mtime.is_dirty() or self.filename.is_dirty(): return # Check file existence first filename = self.filename.get() if not os.path.exists(filename): if self.exists.get(): self.exists.touch() return elif not self.exists.get(): self.exists.touch() return curr_mtime = self.mtime.get() real_mtime = os.stat(filename)[stat.ST_MTIME] if real_mtime != curr_mtime: self.mtime.touch() def param_touched(self, param_name): if param_name == '_naming': self.filename.touch() elif param_name == 'filename': self.exists.touch() self.thumbnail.touch() elif param_name == 'exists': self.mtime.touch() else: super(FileNode, self).param_touched(param_name) def compute(self, param_name): if param_name == 'filename': config = self._naming.get() named = self.flow().get_named(config) self.filename.set(named.path()) elif param_name == 'exists': filename = self.filename.get() if not filename: self.exists.set(False) else: self.exists.set(os.path.exists(filename)) elif param_name == 'mtime': if not self.exists.get(): self.mtime.set(0) else: filename = self.filename.get() self.mtime.set( os.stat(filename)[stat.ST_MTIME] ) elif param_name == 'thumbnail': dir, name = os.path.split(self.filename.get()) thumbnail = os.path.join(dir, '.thumbnails', name+'.png') self.thumbnail.set(thumbnail)