class Correlation(Debatable): """Correlation class""" name = renamer() source = SharedUniqueProperty('source', 'source_correlations') targets = SharedMultipleProperty('targets', 'target_correlations') context = SharedUniqueProperty('context', 'contextualized_correlations') author = SharedUniqueProperty('author') channels = CompositeMultipleProperty('channels', 'subject') comments = CompositeMultipleProperty('comments') def __init__(self, **kwargs): super(Correlation, self).__init__(**kwargs) self.set_data(kwargs) self.type = CorrelationType.weak self.tags = PersistentList() # self.addtoproperty('channels', Channel(title=_("General"))) @property def ends(self): """Return the ends of the correlation""" result = list(self.targets) result.append(self.source) return result @property def channel(self): channels = getattr(self, 'channels', []) return channels[0] if channels else None @property def type_name(self): return CorrelationType.type_name(self.type)
class Commentable(VisualisableElement, Entity): """ A Commentable entity is an entity that can be comment""" name = renamer() comments = CompositeMultipleProperty('comments') def __init__(self, **kwargs): super(Commentable, self).__init__(**kwargs) self.len_comments = 0 def update_len_comments(self): result = len(self.comments) result += sum([c.update_len_comments() for c in self.comments]) self.len_comments = result return self.len_comments def addtoproperty(self, name, value, moving=None): super(Commentable, self).addtoproperty(name, value, moving) if name == 'comments': channel = getattr(self, 'channel', self) channel.len_comments += 1 if self is not channel: self.len_comments += 1 def delfromproperty(self, name, value, moving=None): super(Commentable, self).delfromproperty(name, value, moving) if name == 'comments': channel = getattr(self, 'channel', self) channel.len_comments -= 1 if self is not channel: self.len_comments -= 1
class Object1(Object): composition_u = CompositeUniqueProperty('composition_u', 'shared2_u', False) composition_m = CompositeMultipleProperty('composition_m', 'shared21_u', False) shared_u = SharedUniqueProperty('shared_u', 'shared22_u', False) shared_m = SharedMultipleProperty('shared_m', 'shared23_u', False)
class Debatable(VisualisableElement, Entity): """ A Debatable entity is an entity that can be comment""" channels = CompositeMultipleProperty('channels', 'subject') def __init__(self, **kwargs): super(Debatable, self).__init__(**kwargs) @property def channel(self): channels = getattr(self, 'channels', []) return channels[0] if channels else None def get_channel(self, user): return self.channel def get_title(self, user=None): return getattr(self, 'title', '') def subscribe_to_channel(self, user): channel = getattr(self, 'channel', None) if channel and (user not in channel.members): channel.addtoproperty('members', user) def add_new_channel(self): self.addtoproperty('channels', Channel())
class EventObject(Entity): """EventObject class""" events = CompositeMultipleProperty('events', 'subject') def __init__(self, **kwargs): super(EventObject, self).__init__(**kwargs) self.set_data(kwargs)
class BaseWorkItem(LockableElement, Object): actions = CompositeMultipleProperty('actions') context = None def __init__(self, node): LockableElement.__init__(self) Object.__init__(self) self.node = node self.is_valide = True def _init_actions(self): for action_context in self.node.definition.contexts: action = action_context(self) action.__name__ = action.behavior_id self.addtoproperty('actions', action) @property def process_id(self): return self.node.process.id @property def node_id(self): return self.node.id @property def process(self): return self.node.process def start(self): pass # pragma: no cover def add_action(self, action): action.workitem = self if getattr(action, '__parent__', None): action.__parent__.move( action.__name__, (self, 'actions'), newname=action.behavior_id) else: action.__name__ = action.behavior_id self.addtoproperty('actions', action) def set_actions(self, actions): self.setproperty('actions', []) for action in actions: self.add_action(action) if getattr(action, 'dont_lock', False): action.dont_lock = False action.call(action) def validate(self): raise NotImplementedError # pragma: no cover def concerned_nodes(self): return {self.node}
class BallotBox(VisualisableElement, Entity): """Ballot box class""" name = renamer() votes = CompositeMultipleProperty('votes') def __init__(self, **kwargs): super(BallotBox, self).__init__(**kwargs) self.vote_len = 0 def addtoproperty(self, name, value, moving=None): super(BallotBox, self).addtoproperty(name, value, moving) if name == 'votes': self.vote_len += 1
class ProcessDefinitionContainer(Entity): definitions = CompositeMultipleProperty('definitions', None, False) def __init__(self, **kwargs): super(ProcessDefinitionContainer, self).__init__(**kwargs) def add_definition(self, definition): definition.__name__ = definition.id self.addtoproperty('definitions', definition) definition._init_definition() def get_definition(self, name): # Don't iterate on self.definitions property for performance reason. return self.get(name, None)
class Workspace(VisualisableElement, Entity): """Working group class""" name = renamer() type_title = _('Resources') template = 'pontus:templates/visualisable_templates/object.pt' files = CompositeMultipleProperty('files') working_group = SharedUniqueProperty('working_group', 'workspace') def __init__(self, **kwargs): super(Workspace, self).__init__(**kwargs) self.set_data(kwargs) @property def proposal(self): return self.working_group.proposal
class Preregistration(VisualisableElement, Entity): """Preregistration class""" icon = 'typcn typcn-user-add' templates = { 'default': 'novaideo:views/templates/preregistration_result.pt', 'bloc': 'novaideo:views/templates/preregistration_result.pt' } name = renamer() ballots = CompositeMultipleProperty('ballots') ballot_processes = SharedMultipleProperty('ballot_processes') organization = SharedUniqueProperty('organization') def __init__(self, **kwargs): super(Preregistration, self).__init__(**kwargs) initial_password = kwargs.pop('password', None) if initial_password: initial_password = User.pwd_manager.encode(initial_password) self.initial_password = initial_password self.set_data(kwargs) self.title = self.first_name + ' ' + \ self.last_name def init_deadline(self, date): self.deadline_date = date\ + datetime.timedelta(seconds=DEADLINE_PREREGISTRATION) return self.deadline_date def get_deadline_date(self): if getattr(self, 'deadline_date', None) is not None: return self.deadline_date self.deadline_date = self.created_at\ + datetime.timedelta(seconds=DEADLINE_PREREGISTRATION) return self.deadline_date def has_trusted_email(self, trusted_emails): email = getattr(self, 'email', None) if email and trusted_emails: return any(email.find(t) >= 0 for t in trusted_emails) return True @property def is_expired(self): return datetime.datetime.now(tz=pytz.UTC) > self.get_deadline_date()
class ProcessDefinitionContainer(Entity): definitions = CompositeMultipleProperty('definitions', None, False) def __init__(self, **kwargs): super(ProcessDefinitionContainer, self).__init__(**kwargs) def add_definition(self, definition): definition.__name__ = definition.id self.addtoproperty('definitions', definition) definition._init_definition() def get_definition(self, name): for definition in self.definitions: if definition.id == name: return definition return None
class FlowNode(BPMNElement): incoming = SharedMultipleProperty('incoming', 'target', False) outgoing = SharedMultipleProperty('outgoing', 'source', False) workitems = CompositeMultipleProperty('workitems', None, False) process = SharedUniqueProperty('process', 'nodes', False) def __init__(self, definition, **kwargs): super(FlowNode,self).__init__( definition, **kwargs) def prepare(self): registry = get_current_registry() registry.notify(ActivityPrepared(self)) def __call__(self, transaction): self.start(transaction) def find_executable_paths(self, source_path, source): pass# pragma: no cover def start(self, transaction): raise NotImplementedError# pragma: no cover def play(self, transitions): registry = get_current_registry() registry.notify(ActivityFinished(self)) self.process.play_transitions(self, transitions) def replay_path(self, decision, transaction): pass# pragma: no cover def stop(self): pass# pragma: no cover @property def definition(self): return self.process.definition[self.__name__] def __repr__(self):# pragma: no cover return "%s(%r)" % ( self.__class__.__name__, self.id )
class SignalableEntity(Entity): reports = CompositeMultipleProperty('reports') censoring_reason = CompositeUniqueProperty('censoring_reason') def __init__(self, **kwargs): super(SignalableEntity, self).__init__(**kwargs) self.len_reports = 0 self.init_len_current_reports() @property def subject(self): return self.__parent__ def init_len_current_reports(self): self.len_current_reports = 0 def addtoproperty(self, name, value, moving=None): super(SignalableEntity, self).addtoproperty(name, value, moving) if name == 'reports': self.len_current_reports = getattr(self, 'len_current_reports', 0) self.len_reports = getattr(self, 'len_reports', 0) self.len_current_reports += 1 self.len_reports += 1 def get_decision_ballot(self): ballots = [ b for b in self.ballots if b.group_id == 'vote_moderation' and b.is_finished ] # return only the last ballot ballots = sorted( ballots, key=lambda e: getattr(e, 'release_date', e.modified_at)) if ballots: return ballots[-1] return None
class SignalableEntity(Entity): reports = CompositeMultipleProperty('reports') censoring_reason = CompositeUniqueProperty('censoring_reason') def __init__(self, **kwargs): super(SignalableEntity, self).__init__(**kwargs) self.len_reports = 0 self.init_len_current_reports() @property def subject(self): return self.__parent__ def init_len_current_reports(self): self.len_current_reports = 0 def addtoproperty(self, name, value, moving=None): super(SignalableEntity, self).addtoproperty(name, value, moving) if name == 'reports': self.len_current_reports = getattr(self, 'len_current_reports', 0) self.len_reports = getattr(self, 'len_reports', 0) self.len_current_reports += 1 self.len_reports += 1
class Runtime(Entity): processes = CompositeMultipleProperty('processes', None, False) def getprocesses(self): return self.processes
class ObjectA(Entity): composition_mu = CompositeMultipleProperty('composition_mu', None, False)
class Idea(VersionableEntity, DuplicableEntity, SearchableEntity, CorrelableEntity, PresentableEntity, ExaminableEntity, Node, Emojiable, SignalableEntity, Debatable, Tokenable, EventObject): """Idea class""" type_title = _('Idea') icon = 'icon novaideo-icon icon-idea' templates = {'default': 'novaideo:views/templates/idea_result.pt', 'bloc': 'novaideo:views/templates/idea_bloc.pt', 'small': 'novaideo:views/templates/small_idea_result.pt', 'popover': 'novaideo:views/templates/idea_popover.pt'} template = 'novaideo:views/templates/idea_list_element.pt' name = renamer() author = SharedUniqueProperty('author', 'ideas') organization = SharedUniqueProperty('organization') attached_files = CompositeMultipleProperty('attached_files') url_files = CompositeMultipleProperty('url_files') challenge = SharedUniqueProperty('challenge', 'ideas') ballots = CompositeMultipleProperty('ballots') ballot_processes = SharedMultipleProperty('ballot_processes') opinions_base = OPINIONS def __init__(self, **kwargs): super(Idea, self).__init__(**kwargs) self.set_data(kwargs) self.addtoproperty('channels', Channel()) self.urls = PersistentDict({}) @property def is_workable(self): request = get_current_request() idea_to_examine = 'idea' in request.content_to_examine if idea_to_examine: return True if 'favorable' in self.state else False return True @property def related_proposals(self): """Return all proposals that uses this idea""" return [proposal[0] for proposal in self.get_related_contents( CorrelationType.solid, ['related_proposals'])] @property def related_contents(self): """Return all related contents""" return [content[0] for content in self.all_related_contents] @property def transformed_from(self): """Return all related contents""" transformed_from = [correlation[1].context for correlation in self.get_related_contents( CorrelationType.solid, ['transformation']) if correlation[1].context] return transformed_from[0] if transformed_from else None @property def authors(self): return [self.author] @property def relevant_data(self): return [getattr(self, 'title', ''), getattr(self, 'text', ''), ', '.join(self.branches)] def __setattr__(self, name, value): super(Idea, self).__setattr__(name, value) if name == 'author': self.init_organization() def init_organization(self): if not self.organization: organization = getattr(self.author, 'organization', None) if organization: self.setproperty('organization', organization) def init_published_at(self): setattr(self, 'published_at', datetime.datetime.now(tz=pytz.UTC)) def init_examined_at(self): setattr(self, 'examined_at', datetime.datetime.now(tz=pytz.UTC)) def presentation_text(self, nb_characters=400): return truncate_text(getattr(self, 'text', ""), nb_characters) def get_more_contents_criteria(self): "return specific query, filter values" return None, { 'metadata_filter': { 'content_types': ['proposal', 'idea'] }, 'keywords': list(self.branches) } def get_attached_files_data(self): return get_files_data(self.attached_files) def get_node_descriminator(self): return 'idea' def format(self, request): text = getattr(self, 'text', '') all_urls, url_files, text_urls, formatted_text = text_urls_format( text, request) self.urls = PersistentDict(all_urls) self.setproperty('url_files', url_files) self.formatted_text = formatted_text self.formatted_urls = text_urls
class Comment(Commentable, CorrelableEntity, Emojiable, SignalableEntity): """Comment class""" icon = 'icon ion-chatbubbles' templates = {'default': 'novaideo:views/templates/comment_result.pt'} name = renamer() author = SharedUniqueProperty('author') files = CompositeMultipleProperty('files') url_files = CompositeMultipleProperty('url_files') related_correlation = SharedUniqueProperty('related_correlation', 'targets') contextualized_correlations = SharedMultipleProperty( 'contextualized_correlations', 'context') def __init__(self, **kwargs): super(Comment, self).__init__(**kwargs) self.set_data(kwargs) self.urls = PersistentDict({}) self.edited = False self.pinned = False @property def channel(self): """Return the channel of th commented entity""" if not isinstance(self.__parent__, Comment): return self.__parent__ else: return self.__parent__.channel @property def root(self): """Return the root comment""" if not isinstance(self.__parent__, Comment): return self else: return self.__parent__.root @property def comment_parent(self): """Return the root comment""" if isinstance(self.__parent__, Comment): return self.__parent__ else: return None @property def subject(self): """Return the commented entity""" return self.channel.get_subject() @property def challenge(self): return getattr(self.subject, 'challenge', None) @property def relevant_data(self): return [ getattr(self, 'comment', ''), getattr(self.author, 'title', getattr(self.author, '__name__', '')) ] @property def related_contents(self): subject = self.subject return [ content[0] for content in self.contextualized_contents if content[0] is not subject ] @property def associated_contents(self): subject = self.subject return [ content[0] for content in self.contextualized_contents if content[0] is not subject and not getattr(content[1], 'tags', []) ] def set_associated_contents(self, associated_contents, user): subject = self.subject current_associated_contents = self.associated_contents associated_contents_to_add = [ i for i in associated_contents if i not in current_associated_contents ] associated_contents_to_del = [ i for i in current_associated_contents if i not in associated_contents and i not in associated_contents_to_add ] correlations = connect( subject, associated_contents_to_add, { 'comment': _('Add related contents'), 'type': _('Edit the comment') }, author=user, ) for correlation in correlations: correlation.setproperty('context', self) disconnect(subject, associated_contents_to_del) def get_title(self): return self.subject.title def presentation_text(self, nb_characters=400): return getattr(self, 'comment', "")[:nb_characters] + '...' def get_discuss_url(self, request, user): subject = self.channel.get_subject(user) return request.resource_url(subject, "@@index") + '#comment-' + str( get_oid(self, 'None')) def get_url(self, request): return request.resource_url(request.root, "@@seecomment", query={'comment_id': get_oid(self)}) def get_related_contents(self, user): return [r for r in self.related_contents if can_access(user, r)] def format(self, request, is_html=False): comment = getattr(self, 'comment', '') all_urls, url_files, text_urls, formatted_text = text_urls_format( comment, request, is_html) self.urls = PersistentDict(all_urls) self.setproperty('url_files', url_files) self.formatted_comment = formatted_text self.formatted_urls = text_urls def get_attached_files_data(self): return get_files_data(self.files) def can_add_reaction(self, user, process): return True
class Process(Entity): nodes = CompositeMultipleProperty('nodes', 'process', True) transitions = CompositeMultipleProperty('transitions', 'process', True) execution_context = CompositeUniqueProperty('execution_context', 'process', True) _started = False _finished = False # if attached to a subprocess attachedTo = None def __init__(self, definition, startTransition, **kwargs): super(Process, self).__init__(**kwargs) self.id = definition.id self.global_transaction = Transaction() self.startTransition = startTransition if not self.title: self.title = definition.title if not self.description: self.description = definition.description execution_context = ExecutionContext() execution_context.__name__ = 'execution_context' self.setproperty('execution_context', execution_context) # do a commit so all events have a _p_oid # mail delivery doesn't support savepoint try: transaction.commit() except Exception: transaction.abort() def defineGraph(self, definition): for nodedef in definition.nodes: node = nodedef.create() node.id = nodedef.id node.__name__ = nodedef.__name__ self.addtoproperty('nodes', node) if isinstance(nodedef, EventHandlerDefinition): node._init_boundaryEvents(nodedef) for transitiondef in definition.transitions: transition = transitiondef.create() transition.__name__ = transitiondef.__name__ self.addtoproperty('transitions', transition) transition._init_ends(self, transitiondef) def definition(self): def_container = find_service('process_definition_container') pd = None if def_container is not None: pd = def_container.get_definition(self.id) return pd definition = property(definition) @property def discriminator(self): return self.definition.discriminator @property def isSubProcess(self): return self.definition.isSubProcess def replay_path(self, decision, transaction): path = decision.path first_transitions = decision.first_transitions self.replay_transitions(decision, first_transitions, transaction) executed_transitions = first_transitions next_transitions = set() for transition in first_transitions: next_transitions = next_transitions.union( set(path.next(transition))) for next_transition in set(next_transitions): if next_transition in executed_transitions: next_transitions.remove(next_transition) while next_transitions: self.replay_transitions(decision, next_transitions, transaction) executed_transitions.extend(next_transitions) next_ts = set() for next_transition in next_transitions: next_ts = next_ts.union(set(path.next(next_transition))) for next_transition in list(next_ts): if next_transition in executed_transitions: next_ts.remove(next_transition) next_transitions = next_ts def replay_transitions(self, decision, transitions, transaction): executed_nodes = [] for transition in transitions: node = transition.source if not (node in executed_nodes): executed_nodes.append(node) node.replay_path(decision, transaction) def getWorkItems(self): dace_catalog = find_catalog('dace') process_inst_uid_index = dace_catalog['process_inst_uid'] object_provides_index = dace_catalog['object_provides'] p_uid = get_oid(self, None) query = object_provides_index.any((IWorkItem.__identifier__,)) & \ process_inst_uid_index.any((int(p_uid),)) workitems = query.execute().all() result = {} self.result_multiple = {} # for tests for wi in workitems: if isinstance(wi.node, SubProcess) and wi.node.sub_processes: for sub_process in wi.node.sub_processes: result.update(sub_process.getWorkItems()) if wi.node.id in result: self.result_multiple[wi.node.id].append(wi) else: result[wi.node.id] = wi self.result_multiple[wi.node.id] = [wi] return result def getAllWorkItems(self, node_id=None): dace_catalog = find_catalog('dace') process_inst_uid_index = dace_catalog['process_inst_uid'] object_provides_index = dace_catalog['object_provides'] p_uid = get_oid(self, None) query = object_provides_index.any((IWorkItem.__identifier__,)) & \ process_inst_uid_index.any((int(p_uid),)) if node_id is not None: node_id_index = dace_catalog['node_id'] query = query & node_id_index.eq(self.id + '.' + node_id) workitems = query.execute().all() result = [] for wi in workitems: if wi is None: log.error( 'getAllWorkItems: one of the wi is None for process %s', p_uid) continue if isinstance(wi.node, SubProcess) and wi.node.sub_processes: for sub_process in wi.node.sub_processes: result.extend(sub_process.getAllWorkItems()) if not (wi in result): result.append(wi) return result def start(self): if self._started: raise TypeError("Already started") self._started = True setattr(self.definition, '_isIntanciated_', True) registry = get_current_registry() registry.notify(ProcessStarted(self)) def execute(self): start_events = [self[s.__name__] for s in \ self.definition._get_start_events()] for start_event in start_events: start_event.prepare() start_event.prepare_for_execution() if self._started: break def play_transitions(self, node, transitions): registry = get_current_registry() if transitions: for transition in transitions: next_node = transition.target registry.notify(transition) next_node.prepare() if isinstance(next_node, Event): next_node.prepare_for_execution() for transition in transitions: next_node = transition.target starttransaction = self.global_transaction.start_subtransaction( 'Start', transitions=(transition, ), initiator=transition.source) next_node(starttransaction) if self._finished: break def execute_action(self, context, request, action_id, appstruct, ignor_validation=True): try: workitems = self.getWorkItems() workitem = workitems[self.id + '.' + action_id] action = workitem.actions[0] if not ignor_validation: action.validate(context, request) action.before_execution(context, request) action.execute(context, request, appstruct) return True except Exception: return False def get_actions(self, action_id): try: workitems = self.getWorkItems() workitem = workitems[self.id + '.' + action_id] return workitem.actions except Exception: return [] def reindex(self): event = ObjectModified(self) registry = get_current_registry() registry.subscribers((event, self), None) wis = [n.workitems for n in self.nodes] wis = [item for sublist in wis for item in sublist] actions = [w.actions for w in wis] actions = [item for sublist in actions for item in sublist] for action in actions: action.reindex() def __repr__(self): # pragma: no cover return "Process(%r)" % self.definition.id
class Challenge(SearchableEntity, CorrelableEntity, PresentableEntity, ExaminableEntity, Node, Emojiable, SignalableEntity, Debatable): """Challenge class""" type_title = _('Challenge') icon = 'ion-trophy' templates = { 'default': 'novaideo:views/templates/challenge_result.pt', 'bloc': 'novaideo:views/templates/challenge_card.pt', 'small': 'novaideo:views/templates/small_challenge_result.pt', 'popover': 'novaideo:views/templates/challenge_popover.pt', 'card': 'novaideo:views/templates/challenge_card.pt' } name = renamer() author = SharedUniqueProperty('author', 'challenges') image = CompositeUniqueProperty('image') proposals = SharedMultipleProperty('proposals', 'challenge') ideas = SharedMultipleProperty('ideas', 'challenge') questions = SharedMultipleProperty('questions', 'challenge') attached_files = CompositeMultipleProperty('attached_files') invited_users = SharedMultipleProperty('invited_users') url_files = CompositeMultipleProperty('url_files') def __init__(self, **kwargs): super(Challenge, self).__init__(**kwargs) self.set_data(kwargs) self.addtoproperty('channels', Channel()) self.urls = PersistentDict({}) def __setattr__(self, name, value): super(Challenge, self).__setattr__(name, value) if name in ('deadline', 'published_at', 'created_at') and value: self.init_total_days() @property def related_contents(self): return [content[0] for content in self.all_related_contents] @property def challenge(self): return self @property def transformed_from(self): """Return all related contents""" transformed_from = [ correlation[1].context for correlation in self.get_related_contents( CorrelationType.solid, ['transformation']) if correlation[1].context ] return transformed_from[0] if transformed_from else None @property def is_expired(self): if 'closed' in self.state: return True deadline = getattr(self, 'deadline', None) if deadline is not None: now = datetime.datetime.now(tz=pytz.UTC) return now.date() >= deadline return False @property def can_add_content(self): return not self.is_expired and 'pending' in self.state @property def remaining_duration(self): deadline = getattr(self, 'deadline', None) duration = getattr(self, 'duration', None) if deadline is not None and duration is not None: now = datetime.datetime.now(tz=pytz.UTC) remaining = (deadline - now.date()).days return remaining if remaining >= 0 else 0 return None def init_published_at(self): setattr(self, 'published_at', datetime.datetime.now(tz=pytz.UTC)) def init_support_history(self): if not hasattr(self, '_support_history'): setattr(self, '_support_history', PersistentList()) def init_total_days(self): deadline = getattr(self, 'deadline', None) date = getattr(self, 'published_at', None) date = date if date else getattr(self, 'created_at', None) if deadline is not None and date is not None: duration = (deadline - date.date()).days setattr(self, 'duration', duration) def is_managed(self, root): return root.manage_challenges def get_attached_files_data(self): return get_files_data(self.attached_files) def get_all_attached_files_data(self): files = list(self.attached_files) files.append(self.image) return get_files_data(files) def get_node_descriminator(self): return 'challenge' def format(self, request): text = getattr(self, 'text', '') all_urls, url_files, text_urls, formatted_text = text_urls_format( text, request, True) self.urls = PersistentDict(all_urls) self.setproperty('url_files', url_files) self.formatted_text = formatted_text self.formatted_urls = text_urls
class WorkingGroup(VisualisableElement, Entity): """Working group class""" name = renamer() template = 'pontus:templates/visualisable_templates/object.pt' proposal = SharedUniqueProperty('proposal', 'working_group') members = SharedMultipleProperty('members', 'working_groups') wating_list = SharedMultipleProperty('wating_list') ballots = CompositeMultipleProperty('ballots') improvement_cycle_proc = SharedUniqueProperty('improvement_cycle_proc') workspace = CompositeUniqueProperty('workspace', 'working_group') def init_workspace(self): self.addtoproperty('workspace', Workspace(title="Workspace")) @property def work_mode(self): mode_id = getattr(self, 'work_mode_id', None) if mode_id: return WORK_MODES.get(mode_id, None) root = self.__parent__ if hasattr(root, 'get_work_modes') and len(root.get_work_modes()) == 1: return root.get_default_work_mode() return None @property def challenge(self): return getattr(self.proposal, 'challenge', None) def get_state(self, request, user): return get_states_mapping(user, self, getattr(self, 'state_or_none', [None])[0]) def empty(self, remove_author=True): author = self.proposal.author self.state = PersistentList(['deactivated']) self.setproperty('wating_list', []) if hasattr(self, 'first_improvement_cycle'): del self.first_improvement_cycle if hasattr(self, 'first_vote'): del self.first_vote members = self.members if remove_author and author in members: members.remove(author) for member in members: self.delfromproperty('members', member) revoke_roles(member, (('Participant', self.proposal), )) self.init_nonproductive_cycle() def inc_iteration(self): self.iteration = getattr(self, 'iteration', 0) + 1 def init_nonproductive_cycle(self): self.nonproductive_cycle = 0 def inc_nonproductive_cycle(self): self.nonproductive_cycle = getattr(self, 'nonproductive_cycle', 0) + 1 def is_member(self, user): mask = getattr(user, 'mask', None) return user in self.members or (mask and mask in self.members) def in_wating_list(self, user): mask = getattr(user, 'mask', None) return user in self.wating_list or (mask and mask in self.wating_list) def get_member(self, user): if not self.is_member(user): return None if user in self.members: return user mask = getattr(user, 'mask', None) if mask and mask in self.members: return mask return None def get_member_in_wating_list(self, user): if not self.in_wating_list(user): return None if user in self.wating_list: return user mask = getattr(user, 'mask', None) if mask and mask in self.wating_list: return mask return None
class Person(User, SearchableEntity, CorrelableEntity, Debatable): """Person class""" type_title = _('Person') icon = 'icon glyphicon glyphicon-user' #'icon novaideo-icon icon-user' templates = { 'default': 'novaideo:views/templates/person_result.pt', 'bloc': 'novaideo:views/templates/person_bloc.pt', 'small': 'novaideo:views/templates/small_person_result.pt', 'popover': 'novaideo:views/templates/person_popover.pt', 'card': 'novaideo:views/templates/person_card.pt', 'header': 'novaideo:views/templates/person_header.pt', } default_picture = 'novaideo:static/images/user100.png' name = renamer() tokens = CompositeMultipleProperty('tokens') tokens_ref = SharedMultipleProperty('tokens_ref') organization = SharedUniqueProperty('organization', 'members') events = SharedMultipleProperty('events', 'author') picture = CompositeUniqueProperty('picture') cover_picture = CompositeUniqueProperty('cover_picture') ideas = SharedMultipleProperty('ideas', 'author') selections = SharedMultipleProperty('selections') working_groups = SharedMultipleProperty('working_groups', 'members') wg_participations = SharedMultipleProperty('wg_participations', 'wating_list_participation') old_alerts = SharedMultipleProperty('old_alerts') following_channels = SharedMultipleProperty('following_channels', 'members') folders = SharedMultipleProperty('folders', 'author') questions = SharedMultipleProperty('questions', 'author') challenges = SharedMultipleProperty('challenges', 'author') ballots = CompositeMultipleProperty('ballots') mask = SharedUniqueProperty('mask', 'member') def __init__(self, **kwargs): self.branches = PersistentList() self.keywords = PersistentList() super(Person, self).__init__(**kwargs) kwargs.pop('password', None) self.set_data(kwargs) self.set_title() self.last_connection = datetime.datetime.now(tz=pytz.UTC) self._read_at = OOBTree() self.guide_tour_data = PersistentDict({}) self.confidence_index = 0 self._notes = OOBTree() self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) self.reserved_tokens = PersistentList([]) self._submited_at = OOBTree() self._reported_at = OOBTree() def __setattr__(self, name, value): super(Person, self).__setattr__(name, value) if name == 'organization' and value: self.init_contents_organizations() def get_len_tokens(self, root=None, exclude_reserved_tokens=False): root = root or getSite() return root.tokens_mini if exclude_reserved_tokens \ else root.tokens_mini + len(self.reserved_tokens) def get_len_evaluations(self, exclude_reserved_tokens=False): total = self.len_allocated_tokens.get(Evaluations.support, 0) + \ self.len_allocated_tokens.get(Evaluations.oppose, 0) if exclude_reserved_tokens: return total - len([ o for o in self.reserved_tokens if o in self.allocated_tokens ]) return total def get_len_free_tokens(self, root=None, exclude_reserved_tokens=False): root = root or getSite() return self.get_len_tokens(root, exclude_reserved_tokens) - \ self.get_len_evaluations(exclude_reserved_tokens) def has_token(self, obj=None, root=None): root = root or getSite() obj_oid = get_oid(obj, None) if obj_oid and obj_oid in self.reserved_tokens: return obj_oid not in self.allocated_tokens return self.get_len_free_tokens(root, True) > 0 def add_token(self, obj, evaluation_type, root=None): if self.has_token(obj, root): self.allocated_tokens[get_oid(obj)] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, obj): obj_oid = get_oid(obj) if obj_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(obj_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def add_reserved_token(self, obj): obj_oid = get_oid(obj) if obj_oid not in self.reserved_tokens: self.reserved_tokens.append(obj_oid) def remove_reserved_token(self, obj): obj_oid = get_oid(obj) if obj_oid in self.reserved_tokens: self.reserved_tokens.remove(obj_oid) def evaluated_objs(self, evaluation_type=None): if evaluation_type: return [ get_obj(key) for value, key in self.allocated_tokens.byValue( evaluation_type) ] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluated_objs_ids(self, evaluation_type=None): if evaluation_type: return [ key for value, key in self.allocated_tokens.byValue( evaluation_type) ] return list(self.allocated_tokens.keys()) def init_contents_organizations(self): novaideo_catalog = find_catalog('novaideo') dace_catalog = find_catalog('dace') organizations_index = novaideo_catalog['organizations'] object_authors_index = novaideo_catalog['object_authors'] object_provides_index = dace_catalog['object_provides'] query = object_authors_index.any([get_oid(self)]) & \ object_provides_index.any( [Iidea.__identifier__, IProposal.__identifier__]) & \ organizations_index.any([0]) for entity in query.execute().all(): entity.init_organization() entity.reindex() def set_read_date(self, channel, date): self._read_at[get_oid(channel)] = date def get_read_date(self, channel): return self._read_at.get(get_oid(channel), datetime.datetime.now(tz=pytz.UTC)) def get_channel(self, user): all_channels = list(self.channels) all_channels.extend(list(getattr(user, 'channels', []))) for channel in all_channels: if user in channel.members and self in channel.members: return channel return None def addtoproperty(self, name, value, moving=None): super(Person, self).addtoproperty(name, value, moving) if name == 'selections': value.len_selections = getattr(value, 'len_selections', 0) value.len_selections += 1 def delfromproperty(self, name, value, moving=None): super(Person, self).delfromproperty(name, value, moving) if name == 'selections': value.len_selections = getattr(value, 'len_selections', 0) if value.len_selections > 0: value.len_selections -= 1 def set_title(self): if getattr(self, 'pseudonym', ''): self.title = self.pseudonym else: self.title = getattr(self, 'first_name', '') + ' ' + \ getattr(self, 'last_name', '') def add_note(self, user, context, note, date, time_constant): self._notes[date] = (get_oid(user), get_oid(context), note) self.calculate_confidence_index(time_constant) def get_questions(self, user): if user is self: return self.questions + getattr(self.mask, 'questions', []) return self.questions def get_ideas(self, user): if user is self: return self.ideas + getattr(self.mask, 'ideas', []) return self.ideas def get_working_groups(self, user): if user is self: return self.working_groups + getattr(self.mask, 'working_groups', []) return self.working_groups @property def proposals(self): return [wg.proposal for wg in self.working_groups] def get_proposals(self, user): if user is self: return self.proposals + getattr(self.mask, 'proposals', []) return self.proposals @property def contacts(self): return [s for s in self.selections if isinstance(s, Person)] @property def participations(self): result = [ p for p in list(self.proposals) if any(s in p.state for s in [ 'amendable', 'open to a working group', 'votes for publishing', 'votes for amendments' ]) ] return result def get_participations(self, user): if user is self: return self.participations + getattr(self.mask, 'participations', []) return self.participations @property def contents(self): result = [i for i in list(self.ideas) if i is i.current_version] result.extend(self.proposals) result.extend(self.questions) result.extend(self.challenges) result.extend(self.events) return result def get_contents(self, user): if user is self: return self.contents + getattr(self.mask, 'contents', []) return self.contents @property def active_working_groups(self): return [p.working_group for p in self.participations] def get_active_working_groups(self, user): if user is self: return self.active_working_groups + getattr( self.mask, 'active_working_groups', []) return self.active_working_groups def get_wg_participations(self, user): if user is self: return self.wg_participations + getattr(self.mask, 'wg_participations', []) return self.wg_participations @property def is_published(self): return 'active' in self.state @property def managed_organization(self): return get_objects_with_role(user=self, role='OrganizationResponsible') def get_confidence_index(self): return getattr(self, 'confidence_index', 0) def reindex(self): super(Person, self).reindex() root = getSite() self.__access_keys__ = PersistentList(generate_access_keys(self, root)) def get_picture_url(self, kind, default): if self.picture: img = getattr(self.picture, kind, None) if img: return img.url return default def get_more_contents_criteria(self): "return specific query, filter values" return None, None def set_organization(self, organization): current_organization = self.organization if organization: if current_organization is not organization: is_manager = current_organization and has_role( ('OrganizationResponsible', current_organization), self, ignore_superiors=True) if current_organization and is_manager: revoke_roles( self, (('OrganizationResponsible', current_organization), )) self.setproperty('organization', organization) elif current_organization: is_manager = has_role( ('OrganizationResponsible', current_organization), self, ignore_superiors=True) if is_manager: revoke_roles( self, (('OrganizationResponsible', current_organization), )) self.delfromproperty('organization', current_organization) @property def all_alerts(self): novaideo_catalog = find_catalog('novaideo') dace_catalog = find_catalog('dace') alert_keys_index = novaideo_catalog['alert_keys'] alert_exclude_keys_index = novaideo_catalog['alert_exclude_keys'] object_provides_index = dace_catalog['object_provides'] exclude = [str(get_oid(self))] if self.mask: exclude.append(str(get_oid(self.mask))) query = object_provides_index.any([IAlert.__identifier__]) & \ alert_keys_index.any(self.get_alerts_keys()) & \ alert_exclude_keys_index.notany(exclude) return query.execute() @property def alerts(self): old_alerts = [get_oid(a) for a in self.old_alerts] result = self.all_alerts def exclude(result_set, docids): filtered_ids = list(result_set.ids) for _id in docids: if _id in docids and _id in filtered_ids: filtered_ids.remove(_id) return result_set.__class__(filtered_ids, len(filtered_ids), result_set.resolver) return exclude(result, old_alerts) def get_alerts_keys(self): result = ['all', str(get_oid(self))] if self.mask: result.append(str(get_oid(self.mask))) return result def get_alerts(self, alerts=None, kind=None, subject=None, **kwargs): if alerts is None: alerts = self.alerts if kind: alerts = [a for a in alerts if a.is_kind_of(kind)] if subject: alerts = [a for a in alerts if subject in a.subjects] if kwargs: alerts = [a for a in alerts if a.has_args(**kwargs)] return alerts def calculate_confidence_index(self, time_constant): now = datetime.datetime.utcnow().timestamp() notes = np.array([v[2] for v in self._notes.values()]) dates = np.array([int(t.timestamp()) for t in self._notes.keys()]) time_c = time_constant * 86400 confidence_index = np.sum( np.dot(notes, np.exp(-np.log(2) * (now - dates) / time_c))) self.confidence_index = round(confidence_index, 1) @property def user_groups(self): groups = list(self.groups) if self.organization: groups.append(self.organization) if self.mask: groups.append(self.mask) return groups @property def user_locale(self): locale = getattr(self, 'locale', None) if not locale: locale = getSite(self).locale return locale def _init_mask(self, root): if not self.mask: mask = Mask() root.addtoproperty('masks', mask) self.setproperty('mask', mask) def get_mask(self, root=None): root = root if root else getSite() if not getattr(root, 'anonymisation', False): return self self._init_mask(root) return self.mask def add_submission(self, obj): now = datetime.datetime.now(tz=pytz.UTC) self._submited_at[now] = get_oid(obj) def add_report(self, obj): now = datetime.datetime.now(tz=pytz.UTC) self._reported_at[now] = get_oid(obj) def can_submit_idea(self, root=None): root = root if root else getSite() now = datetime.datetime.now(tz=pytz.UTC) monday = datetime.datetime.combine((now - datetime.timedelta(days=7)), datetime.time(0, 0, 0, tzinfo=pytz.UTC)) return len(self._submited_at.values(min=monday, max=now)) < getattr( root, 'nb_submission_maxi', 3) def can_report(self, root=None): root = root if root else getSite() now = datetime.datetime.now(tz=pytz.UTC) monday = datetime.datetime.combine((now - datetime.timedelta(days=7)), datetime.time(0, 0, 0, tzinfo=pytz.UTC)) return len(self._reported_at.values(min=monday, max=now)) < getattr( root, 'nb_reports_maxi', 3)
class SiteFolder(VisualisableElement, ServiceableEntity): """SiteFolder class""" icon = 'glyphicon glyphicon-globe' templates = { 'default': 'lac:views/templates/site_folder_result.pt', 'bloc': 'lac:views/templates/site_folder_result.pt' } name = renamer() tree = synchronize_tree() files = CompositeMultipleProperty('files') newsletters = CompositeMultipleProperty('newsletters', 'site') picture = CompositeUniqueProperty('picture') favicon = CompositeUniqueProperty('favicon') extraction_template = CompositeUniqueProperty('extraction_template') theme = CompositeUniqueProperty('theme') customer = SharedUniqueProperty('customer', 'sites') applications = CompositeMultipleProperty('applications', 'site') # controleur de publication current_cover = CompositeUniqueProperty('current_cover') alerts = CompositeMultipleProperty('alerts') def __init__(self, **kwargs): super(SiteFolder, self).__init__(**kwargs) self.set_data(kwargs) self.init_informations() self._keywords_ = [] self._init_keywords() @property def mail_conf(self): return self.get_data( omit(MailTemplatesConfigurationSchema(), '_csrf_token_')) @property def filter_conf(self): result = self.get_data( omit(FilterConfigurationSchema(), '_csrf_token_')) return result @property def pub_conf(self): return self.get_data( omit(PublicationConfigurationSchema(), '_csrf_token_')) @property def ui_conf(self): return self.get_data( omit(UserInterfaceConfigurationSchema(), '_csrf_token_')) @property def keywords_conf(self): return self.get_data(omit(KeywordsConfSchema(), '_csrf_token_')) @property def other_conf(self): return self.get_data(omit(OtherSchema(), '_csrf_token_')) @property def real_closing_date(self): now = datetime.datetime.now(tz=pytz.UTC) closing_date = getattr(self, 'closing_date', _marker) closing_frequence = getattr(self, 'closing_frequence', 0) if closing_date is _marker: closing_date = now last_closing_date = closing_date - datetime.timedelta( days=closing_frequence) if now < last_closing_date: return last_closing_date return closing_date @property def publication_date(self): closing_date = self.real_closing_date closing_frequence = getattr(self, 'closing_frequence', 0) delay_before_publication = getattr(self, 'delay_before_publication', 0) delay = delay_before_publication - closing_frequence return datetime.timedelta(days=delay) + closing_date @property def sections(self): levels = self.get_keywords_by_level() if len(levels) >= 2: return sorted(levels[1]) return [] def __setattr__(self, name, value): super(SiteFolder, self).__setattr__(name, value) if name == 'filters': self._init_keywords() def init_informations(self): self.closing_frequence = DEFAULT_CLOSING_FREQUENCE self.delay_before_publication = DEFAULT_DELAY_BEFORE_PUBLICATION self.days_visibility = DEFAULT_DAYS_VISIBILITY self.publication_number = 0 self.closing_date = datetime.datetime.now(tz=pytz.UTC) +\ datetime.timedelta(days=self.closing_frequence) self._tree = PersistentDict() self.keywords = PersistentList() self.tree = DEFAULT_TREE self.init_files() def get_keywords_by_level(self): return get_keywords_by_level(dict(self.tree), ROOT_TREE) def get_tree_nodes_by_level(self): return get_tree_nodes_by_level(dict(self.tree)) def get_all_branches(self): return get_all_branches(self.tree) def merge_tree(self, tree): mapping = getattr(self, 'keywords_mapping', {}).get('mapping', []) self.tree = merge_tree(dict(self.tree), tree, mapping) def get_normalized_tree(self, tree, type_='out'): mapping = getattr(self, 'keywords_mapping', {}).get('mapping', []) return normalize_tree(tree, mapping, type_) def get_normalized_keywords(self, keywords, type_='out'): mapping = getattr(self, 'keywords_mapping', {}).get('mapping', []) return normalize_keywords(keywords, mapping, type_) def get_tree_branches(self): return get_branches(getattr(self, 'tree', {})) def init_files(self): for information in DEFAULT_SITE_INFORMATIONS: if not self.get(information['name'], None): info_file = FileEntity(title=information['title']) info_file.text = information['content'] info_file.__name__ = information['name'] self.addtoproperty('files', info_file) def next_publication_date(self, week_number=0): closing_date = self.real_closing_date delay_before_publication = getattr(self, 'delay_before_publication', 0) days = getattr(self, 'closing_frequence', 0) * week_number return datetime.timedelta(days=delay_before_publication) +\ closing_date +\ datetime.timedelta(days=days) def get_mail_template(self, id): for mail in getattr(self, 'mail_templates', {}): if mail.get('mail_id', None) == id: return mail template = DEFAULT_SITE_MAILS.get(id, None) if template: template = template.copy() template['mail_id'] = id return template def get_site_sender(self): registry = get_current_registry() default_sender = registry.settings['lac.admin_email'] return getattr(self, 'site_sender', default_sender) def _init_keywords(self): alltrees = [ f.get('metadata_filter', {}).get('tree', {}) for f in getattr(self, 'filters', []) ] keywords = [tree_to_keywords(tree) for tree in alltrees] keywords = list(set([item for sublist in keywords for item in sublist])) self._keywords_ = keywords def get_all_keywords(self): if hasattr(self, '_keywords_'): return self._keywords_ self._init_keywords() return self._keywords_.copy() def get_group(self): if not self.customer: return [] sites = list(self.customer.sites) if self in sites: sites.remove(self) return sites
class SearchableEntity(ServiceableEntity): """ A Searchable entity is an entity that can be searched""" icon = 'glyphicon glyphicon-question-sign' templates = {'default': 'lac:views/templates/default_result.pt', 'bloc': 'lac:views/templates/default_result_bloc.pt', 'extraction': 'lac:views/templates/extraction/default_result.pt'} tree = synchronize_tree() type_title = '' files = CompositeMultipleProperty('files') connections_from = SharedMultipleProperty( 'connections_from', 'connections_to') connections_to = SharedMultipleProperty( 'connections_to', 'connections_from') visibility_dates = dates('visibility_dates') labels = SharedMultipleProperty('labels') def __init__(self, **kwargs): super(SearchableEntity, self).__init__(**kwargs) self._tree = PersistentDict() self.keywords = PersistentList() self.set_data(kwargs) self.source_site = get_oid(get_site_folder(True), None) self._keywords_ = [] self._init_keywords() def __setattr__(self, name, value): super(SearchableEntity, self).__setattr__(name, value) if name == 'description': self._init_presentation_text() @property def object_labels(self): return {'labels': self.labels} @property def metadata(self): return self.get_data(omit(MetadataSchema(), '_csrf_token_')) @property def is_published(self): return 'published' in self.state @property def object_id(self): source_data = getattr(self, 'source_data', {}) obj_id = source_data.get('id', '') + '_' +\ source_data.get('source_id', '') if obj_id == '_': obj_id = str(getattr(self, '__oid__', None))+'_lac' return obj_id @property def json_tree(self): return json.dumps(dict(self.tree)) @property def is_imported(self): source_id = getattr(self, 'source_data', {}).get('source_id', None) return True if source_id and source_id in list(IMPORT_SOURCES.keys())\ else False @property def relevant_data(self): return [getattr(self, 'title', ''), getattr(self, 'description', ''), ', '.join(self.keywords)] @property def sections(self): tree = dict(self.get_normalized_tree()) levels = get_keywords_by_level(tree, ROOT_TREE) if len(levels) >= 2: return sorted(levels[1]) return [] @property def substitutions(self): return [self] def _init_presentation_text(self): pass def _init_keywords(self): pass def get_all_keywords(self): return self.keywords def get_more_contents_criteria(self): "return specific query, filter values" keywords = list(self.keywords) root_tree = ROOT_TREE.lower() if root_tree in keywords: keywords.remove(root_tree) return None, {'keywords': keywords} def get_release_date(self): return getattr(self, 'release_date', self.modified_at) def presentation_text(self, nb_characters=400): return getattr(self, 'description', "")[:nb_characters]+'...' def get_normalized_tree(self, type_='out'): site = get_site_folder(True) return site.get_normalized_tree(getattr(self, 'tree', {}), type_) def get_visibility_filter(self): registry = get_current_registry() return { 'metadata_filter': { 'tree': deepcopy(self.tree), 'content_types': [registry.content.typeof(self)], 'states': list(getattr(self, 'state', [])) } } def set_metadata(self, appstruct): if 'site' not in appstruct: site = get_site_folder(True) if 'accessibility' in appstruct: object_set_access_control(appstruct.get('accessibility'), self, site) appstruct.pop('accessibility') data = {} for key in MetadataSchema(): name = key.name if name in appstruct: data[name] = appstruct.pop(name) if 'object_labels' in data: new_labels = [label['_object_data'] for label in data['object_labels']['new_labels']] all_labels = [] if new_labels: root = site.__parent__ for label in new_labels: root.addtoproperty('labels', label) all_labels.append(label) all_labels.extend(data['object_labels']['labels']) data['labels'] = all_labels data.pop('object_labels') self.set_data(data) self.release_date = getattr( self, 'release_date', self.created_at).replace( tzinfo=pytz.UTC) def labels_data(self, site): labels = getattr(self, 'labels', []) labels_data = [{'title': l.title, 'img': l.picture.url} for l in labels] site_oid = get_oid(site) if site_oid != self.source_site: orig_site = get_obj(self.source_site) if orig_site.favicon: labels_data.append({'title': orig_site.title, 'img': orig_site.favicon.url}) return labels_data
class Event(SearchableEntity): """Event class""" type_title = _('Discussion event') icon = 'glyphicon glyphicon-calendar' templates = { 'default': 'novaideo:views/templates/event_result.pt', 'bloc': 'novaideo:views/templates/event_bloc.pt', 'small': 'novaideo:views/templates/small_event_result.pt', 'popover': 'novaideo:views/templates/event_popover.pt' } template = 'novaideo:views/templates/event_list_element.pt' name = renamer() author = SharedUniqueProperty('author', 'events') subject = SharedUniqueProperty('subject', 'events') url_files = CompositeMultipleProperty('url_files') def __init__(self, **kwargs): super(Event, self).__init__(**kwargs) self.set_data(kwargs) self.urls = PersistentDict({}) @property def relevant_data(self): subject_relevant_data = getattr(self.subject, 'relevant_data', []) subject_relevant_data.extend([getattr(self, 'title', '')]) return subject_relevant_data def init_published_at(self): setattr(self, 'published_at', datetime.datetime.now(tz=pytz.UTC)) def presentation_text(self, nb_characters=400): return truncate_text(getattr(self, 'text', ""), nb_characters) def update(self): if 'expired' not in self.state and self.is_expired: self.state = PersistentList(['expired']) self.reindex() @property def is_expired(self): if 'expired' in self.state: return True try: now = datetime.datetime.now(tz=pytz.timezone(self.tzname)) return self.date < now except Exception as e: return True def get_description(self, request, locale=None): return get_event_description_template(request, locale) def get_mode(self): return KINDS.get(self.kind) def get_locale(self): return EUROPEAN_LOCALES.get(self.locale) def format(self, request): text = getattr(self, 'text', '') all_urls, url_files, text_urls, formatted_text = text_urls_format( text, request) self.urls = PersistentDict(all_urls) self.setproperty('url_files', url_files) self.formatted_text = formatted_text self.formatted_urls = text_urls
class NovaIdeoApplication(CorrelableEntity, Debatable, Application): """Nova-Ideo class (Root)""" name = renamer() preregistrations = CompositeMultipleProperty('preregistrations') challenges = CompositeMultipleProperty('challenges') working_groups = CompositeMultipleProperty('working_groups') proposals = CompositeMultipleProperty('proposals') organizations = CompositeMultipleProperty('organizations') invitations = CompositeMultipleProperty('invitations') ideas = CompositeMultipleProperty('ideas') questions = CompositeMultipleProperty('questions') correlations = CompositeMultipleProperty('correlations') files = CompositeMultipleProperty('files') alerts = CompositeMultipleProperty('alerts') picture = CompositeUniqueProperty('picture') homepage_picture = CompositeUniqueProperty('homepage_picture') favicon = CompositeUniqueProperty('favicon') theme = CompositeUniqueProperty('theme') proposal_template = CompositeUniqueProperty('proposal_template') advertisings = CompositeMultipleProperty('advertisings') news_letter_members = SharedMultipleProperty('news_letter_members') general_chanel = SharedUniqueProperty('general_chanel') newsletters = CompositeMultipleProperty('newsletters') smart_folders = CompositeMultipleProperty('smart_folders') connectors = CompositeMultipleProperty('connectors') masks = CompositeMultipleProperty('masks') def __init__(self, **kwargs): super(NovaIdeoApplication, self).__init__(**kwargs) self.keywords = PersistentList() self.initialization() def __setattr__(self, name, value): super(NovaIdeoApplication, self).__setattr__(name, value) if name == 'mail_templates' and value: result = {} for template in value: mail_id = template.get('mail_id') languages = template.get('languages', []) languages = {m['locale']: m for m in languages} result[mail_id] = { 'title': template.get('title'), 'languages': languages } self._mail_templates = PersistentDict(result) @property def mail_conf(self): return self.get_data( omit(MailTemplatesConfigurationSchema(), '_csrf_token_')) @property def work_conf(self): result = self.get_data( omit(WorkParamsConfigurationSchema(), '_csrf_token_')) return result @property def user_conf(self): return self.get_data( omit(UserParamsConfigurationSchema(), '_csrf_token_')) @property def keywords_conf(self): return self.get_data(omit(KeywordsConfSchema(), '_csrf_token_')) @property def ui_conf(self): return self.get_data( omit(UserInterfaceConfigurationSchema(), '_csrf_token_')) @property def homepage_conf(self): return self.get_data( omit(HomepageConfigurationSchema(), '_csrf_token_')) @property def other_conf(self): return self.get_data(omit(OtherSchema(), '_csrf_token_')) @property def notif_conf(self): return self.get_data( omit(NotificationConfigurationSchema(), '_csrf_token_')) def get_newsletters_for_registration(self): return [ nl for nl in self.newsletters if getattr(nl, 'propose_to_registration', True) ] def get_newsletters_automatic_registration(self): """Get newsletters with automatic registration""" return [ nl for nl in self.newsletters if getattr(nl, 'automatic_registration', True) ] def initialization(self): self.reset_default_values() self.deadlines = PersistentList([datetime.datetime.now(tz=pytz.UTC)]) self.work_modes = list(WORK_MODES.keys()) self.colors_mapping = PersistentDict(DEFAULT_COLORS) def init_channels(self): if not self.general_chanel: self.addtoproperty('channels', Channel(title=_("General"))) self.setproperty('general_chanel', self.channels[0]) def reset_default_values(self): self.participants_mini = 3 self.participants_maxi = 12 self.participations_maxi = 5 self.tokens_mini = 7 @property def moderate_proposals(self): return 'proposal' in getattr(self, 'content_to_moderate', []) @property def moderate_ideas(self): return 'idea' in getattr(self, 'content_to_moderate', []) @property def examine_proposals(self): return 'proposal' in getattr(self, 'content_to_examine', []) @property def examine_ideas(self): return 'idea' in getattr(self, 'content_to_examine', []) @property def support_proposals(self): return 'proposal' in getattr(self, 'content_to_support', []) @property def support_ideas(self): return 'idea' in getattr(self, 'content_to_support', []) @property def manage_challenges(self): return 'challenge' in getattr(self, 'content_to_manage', DEFAULT_CONTENT_TO_MANAGE) @property def manage_questions(self): return 'question' in getattr(self, 'content_to_manage', DEFAULT_CONTENT_TO_MANAGE) @property def manage_proposals(self): return 'proposal' in getattr(self, 'content_to_manage', DEFAULT_CONTENT_TO_MANAGE) @property def titles(self): return DEFAULT_TITLES @property def comment_intentions(self): return DEFAULT_COMMENT_INTENTIONS @property def correlation_intentions(self): return DEFAULT_CORRELATION_INTENTIONS @property def idea_intentions(self): return DEFAULT_IDEA_INTENTIONS @property def amendment_intentions(self): return DEFAULT_AMENDMENT_INTENTIONS @property def channel(self): return getattr(self, 'general_chanel', None) def get_nonproductive_cycle_nb(self): return getattr(self, 'nonproductive_cycle_nb', 2) def init_files(self): for information in DEFAULT_FILES: if not self.get(information['name'], None): info_file = FileEntity(title=information['title']) content = information.get('content', '') content_file = information.get('content_file', None) if content_file: content_path = os.path.join(os.path.dirname(__file__), 'static', 'default_files', content_file) if os.path.exists(content_path): content = open(content_path).read() info_file.text = content info_file.__name__ = information['name'] self.addtoproperty('files', info_file) info_file.state = PersistentList(['draft']) setattr(self, information['name'], info_file) def get_mail_template(self, id, locale=None): if locale is None: locale = self.locale mail = getattr(self, '_mail_templates', {}).get(id, None) if not mail: mail = DEFAULT_SITE_MAILS.get(id, None) template = mail.get('languages').get(locale, None) if not template: template = mail.get('languages').get(self.locale, None) return template def get_mail(self, id): for mail in getattr(self, 'mail_templates', {}): if mail.get('mail_id', None) == id: return mail template = DEFAULT_SITE_MAILS.get(id, None) if template: template = template.copy() template['mail_id'] = id template['languages'] = list(template['languages'].values()) return template def get_site_sender(self): registry = get_current_registry() default_sender = registry.settings['mail.default_sender'] return default_sender def get_work_modes(self): modes = getattr(self, 'work_modes', []) modes = {m: WORK_MODES[m] for m in modes if m in WORK_MODES} if modes: return modes return WORK_MODES def get_default_work_mode(self): modes = list(self.get_work_modes().values()) modes = sorted(modes, key=lambda e: e.order) return modes[0] def add_colors_mapping(self, keys): if not hasattr(self, 'colors_mapping'): self.colors_mapping = PersistentDict(DEFAULT_COLORS) new_keywords = [k for k in keys if k not in self.colors_mapping] colors = random_color(len(new_keywords)) for index, keyword in enumerate(new_keywords): self.colors_mapping[keyword] = {'color': colors[index]} def get_color(self, key): if key in getattr(self, 'colors_mapping', {}): return self.colors_mapping[key] self.add_colors_mapping([key]) return self.colors_mapping[key] def merge_keywords(self, newkeywords): current_keywords = list(self.keywords) current_keywords.extend(newkeywords) self.keywords = PersistentList(list(set(current_keywords))) def get_title(self, user=None): return getattr(self, 'title', '') def get_connectors(self, connector_id): return filter(lambda c: c.connector_id == connector_id, self.connectors)
class Tokenable(Entity): """Question class""" tokens_opposition = CompositeMultipleProperty('tokens_opposition') tokens_support = CompositeMultipleProperty('tokens_support') def __init__(self, **kwargs): super(Tokenable, self).__init__(**kwargs) self.set_data(kwargs) self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) def add_token(self, user, evaluation_type): user_oid = get_oid(user) if user_oid in self.allocated_tokens: self.remove_token(user) self.allocated_tokens[user_oid] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, user): user_oid = get_oid(user) if user_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(user_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def evaluators(self, evaluation_type=None): if evaluation_type: return [ get_obj(key) for value, key in self.allocated_tokens.byValue( evaluation_type) ] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluation(self, user): user_oid = get_oid(user, None) return self.allocated_tokens.get(user_oid, None) def remove_tokens(self, force=False): evaluators = self.evaluators() for user in evaluators: user.remove_token(self) if force: self.remove_token(user) def user_has_token(self, user, root=None): if hasattr(user, 'has_token'): return user.has_token(self, root) return False def init_support_history(self): # [(user_oid, date, support_type), ...], support_type = {1:support, 0:oppose, -1:withdraw} if not hasattr(self, '_support_history'): setattr(self, '_support_history', PersistentList()) @property def len_support(self): return self.len_allocated_tokens.get(Evaluations.support, 0) @property def len_opposition(self): return self.len_allocated_tokens.get(Evaluations.oppose, 0)
class Proposal(VersionableEntity, SearchableEntity, DuplicableEntity, CorrelableEntity, PresentableEntity, ExaminableEntity, Node, Emojiable, SignalableEntity, Debatable, Tokenable): """Proposal class""" type_title = _('Proposal') icon = 'icon novaideo-icon icon-proposal' templates = {'default': 'novaideo:views/templates/proposal_result.pt', 'small': 'novaideo:views/templates/small_proposal_result.pt', 'popover': 'novaideo:views/templates/proposal_popover.pt', 'bloc': 'novaideo:views/templates/proposal_bloc.pt'} template = 'novaideo:views/templates/proposal_list_element.pt' name = renamer() author = SharedUniqueProperty('author') organization = SharedUniqueProperty('organization') working_group = SharedUniqueProperty('working_group', 'proposal') amendments = CompositeMultipleProperty('amendments', 'proposal') corrections = CompositeMultipleProperty('corrections', 'proposal') attached_files = SharedMultipleProperty('attached_files') challenge = SharedUniqueProperty('challenge', 'proposals') opinions_base = OPINIONS def __init__(self, **kwargs): super(Proposal, self).__init__(**kwargs) self.set_data(kwargs) self._amendments_counter = 1 self.addtoproperty('channels', Channel()) @property def related_ideas(self): return [idea[0] for idea in self.get_related_contents( CorrelationType.solid, ['related_ideas'])] @property def related_contents(self): return [content[0] for content in self.all_related_contents] @property def is_published(self): return 'published' in self.state @property def authors(self): authors = self.working_group.members author = self.author if author not in authors: authors.append(author) return authors @property def workspace(self): working_group = self.working_group if working_group: return working_group.workspace if self.current_version is not self: return getattr(self.current_version, 'workspace', None) return None def __setattr__(self, name, value): super(Proposal, self).__setattr__(name, value) if name == 'author': self.init_organization() def init_organization(self): if not self.organization: organization = getattr(self.author, 'organization', None) if organization: self.setproperty('organization', organization) def init_published_at(self): setattr(self, 'published_at', datetime.datetime.now(tz=pytz.UTC)) def init_examined_at(self): setattr(self, 'examined_at', datetime.datetime.now(tz=pytz.UTC)) def is_managed(self, root): return root.manage_proposals def get_more_contents_criteria(self): "return specific query, filter values" return None, { 'metadata_filter': { 'content_types': ['proposal', 'idea'], 'keywords': list(self.keywords) } } def get_attached_files_data(self): return get_files_data(self.attached_files) def set_related_ideas(self, relatedideas, user): current_related_ideas = self.related_ideas related_ideas_to_add = [i for i in relatedideas if i not in current_related_ideas] related_ideas_to_del = [i for i in current_related_ideas if i not in relatedideas and i not in related_ideas_to_add] connect(self, related_ideas_to_add, {'comment': _('Add related ideas'), 'type': _('Edit the proposal')}, user, ['related_proposals', 'related_ideas'], CorrelationType.solid, True) disconnect(self, related_ideas_to_del, 'related_ideas', CorrelationType.solid) def get_version(self, user, parent): old_version = self.version copy_of_proposal = copy( self, parent, omit=('len_selections', 'graph'), roles=True) if old_version: copy_of_proposal.setproperty('version', old_version) copy_of_proposal.init_graph() copy_of_proposal.state = PersistentList(['version', 'archived']) copy_of_proposal.setproperty('author', self.author) copy_of_proposal.setproperty('originalentity', self.originalentity) for amendment in self.amendments: copy_of_proposal.addtoproperty('amendments', amendment) for correction in self.corrections: copy_of_proposal.addtoproperty('corrections', correction) for file_ in self.attached_files: copy_of_proposal.addtoproperty('attached_files', file_) copy_of_proposal.set_related_ideas( self.related_ideas, user) copy_of_proposal.reindex() return copy_of_proposal def remove_tokens(self, force=False): evaluators = self.evaluators() for user in evaluators: user.remove_token(self) user.remove_reserved_token(self) if force: self.remove_token(user) def get_node_descriminator(self): return 'proposal'
class File(DaceObject, OriginFile): variants = CompositeMultipleProperty('variants') def __init__(self, fp, mimetype=None, filename=None, **kwargs): if not filename: filename = self.title DaceObject.__init__(self, **kwargs) if fp: fp.seek(0) else: fp = None if not mimetype or mimetype == 'application/x-download': hint = USE_MAGIC else: hint = mimetype OriginFile.__init__(self, fp, hint, filename) self.set_data(kwargs) @property def fp(self): return self.blob.open('r') @property def filename(self): return self.title @property def uid(self): return str(get_oid(self, None)) @property def is_image(self): return self.mimetype.startswith('image') @property def url(self): request = get_current_request() return request.resource_url(self) def set_data(self, appstruct, omit=('_csrf_token_', '__objectoid__')): super(File, self).set_data(appstruct, omit) if not appstruct.get('elementary', False): try: self.generate_variants() except Exception as e: log.warning(e) def generate_variants(self): if self.is_image: results = generate_images(self.fp, self.filename) self.setproperty('variants', []) for img in results: img_val = Image(img['fp'], self.mimetype, self.filename, elementary=True) img_val.__name__ = img['id'] self.addtoproperty('variants', img_val) try: delattr(self, img_val.__name__) except AttributeError: pass def __getattr__(self, name): if name in AVAILABLE_FORMATS: attr = self.get(name) if attr is None: raise AttributeError(name) return attr else: return super(File, self).__getattr__(name) def get_data(self, node): result = {} result['filename'] = self.title result['uid'] = getattr(self, 'uid', None) result['mimetype'] = self.mimetype result['size'] = self.get_size() result['fp'] = self.blob.open('r') return result def get_size(self): try: return OriginFile.get_size(self) except BlobError: transaction.commit() return OriginFile.get_size(self) def __setattr__(self, name, value): if name == 'fp': self.blob = Blob() self.upload(value, mimetype_hint=USE_MAGIC) elif name == 'filename': self.title = value elif name == 'uid': pass else: super(File, self).__setattr__(name, value) def copy(self): data = self.get_data(None) data.pop('uid') return self.__class__(**data)
class ProcessDefinition(Entity): nodes = CompositeMultipleProperty('nodes', 'process', True) transitions = CompositeMultipleProperty('transitions', 'process', True) TransitionDefinitionFactory = TransitionDefinition isControlled = False isSubProcess = False isVolatile = False isUnique = False discriminator = 'Application' def __init__(self, **kwargs): super(ProcessDefinition, self).__init__(**kwargs) self.contexts = PersistentList() self.id = None if 'id' in kwargs: self.id = kwargs['id'] def _init_definition(self): pass def __call__(self, **kwargs): return Process(self, self._startTransition, **kwargs) def _dirty(self): try: del self._startTransition except AttributeError: pass def __repr__(self): # pragma: no cover return "ProcessDefinition(%r)" % self.id def defineNodes(self, **nodes): self._dirty() for name, node in nodes.items(): node.id = self.id + '.' + name node.__name__ = name self.addtoproperty('nodes', node) if hasattr(node, 'init_process_contexts'): node.init_process_contexts(self) def defineTransitions(self, *transitions): self._dirty() for transition in transitions: transition.__name__ = transition.id self.addtoproperty('transitions', transition) transition._init_ends() def _is_start_orphan(self, node): return not isinstance(node, StartEventDefinition) and not node.incoming def _is_end_orphan(self, node): return not isinstance(node, EndEventDefinition) and not node.outgoing def _normalize_definition(self): new_transitions = () orphan_nodes = [node for node in self.nodes \ if self._is_start_orphan(node)] if orphan_nodes: start_events = self._get_start_events() empty_start_event = None if start_events: for s_e in start_events: if s_e.eventKind is None: empty_start_event = s_e break if empty_start_event is None: empty_start_event = StartEventDefinition() p_g = ParallelGatewayDefinition() if not (empty_start_event in self.nodes): self.defineNodes(emptystart=empty_start_event, startpg=p_g) else: self.defineNodes(startpg=p_g) oldtransitions = list(empty_start_event.outgoing) for oldtransition in oldtransitions: oldtransition.set_source(p_g) new_transitions += (TransitionDefinition( empty_start_event.__name__, 'startpg'), ) for o_n in orphan_nodes: new_transitions += (TransitionDefinition( 'startpg', o_n.__name__), ) if new_transitions: self.defineTransitions(*new_transitions) new_transitions = () orphan_nodes = [node for node in self.nodes \ if self._is_end_orphan(node)] if orphan_nodes: end_events = self._get_end_events() empty_end_event = None if end_events: for e_e in end_events: if e_e.eventKind is None: empty_end_event = e_e break if empty_end_event is None: empty_end_event = EndEventDefinition() e_g = ExclusiveGatewayDefinition() if not (empty_end_event in self.nodes): self.defineNodes(emptyend=empty_end_event, endeg=e_g) else: self.defineNodes(endeg=e_g) oldtransitions = list(empty_end_event.incoming) for oldtransition in oldtransitions: oldtransition.set_target(e_g) new_transitions += (TransitionDefinition( 'endeg', empty_end_event.__name__), ) for o_n in orphan_nodes: new_transitions += (TransitionDefinition( o_n.__name__, 'endeg'), ) if new_transitions: self.defineTransitions(*new_transitions) self._normalize_startevents() self._normalize_endevents() def _normalize_startevents(self): start_events = self._get_start_events() for s_e in start_events: if len(s_e.outgoing) > 1: p_g = ParallelGatewayDefinition() self.defineNodes(mergepg=p_g) oldtransitions = list(s_e.outgoing) for oldtransition in oldtransitions: oldtransition.set_source(p_g) self.defineTransitions( TransitionDefinition(s_e.__name__, 'mergepg')) def _normalize_endevents(self): end_events = self._get_end_events() for e_e in end_events: if len(e_e.incoming) > 1: e_g = ExclusiveGatewayDefinition() self.defineNodes(mergeeg=e_g) oldtransitions = list(e_e.incoming) for oldtransition in oldtransitions: oldtransition.set_target(e_g) self.defineTransitions( TransitionDefinition('mergeeg', e_e.__name__)) def _get_start_events(self): result = [] for node in self.nodes: if isinstance(node, StartEventDefinition): result.append(node) return result def _get_end_events(self): result = [] for node in self.nodes: if isinstance(node, EndEventDefinition): result.append(node) return result @reify def _startTransition(self): start_events = self._get_start_events() if len(start_events) != 1: raise InvalidProcessDefinition("Multiple start events", [id for (id, a) in start_events]) return start_events[0].outgoing[0] def start_process(self, node_name=None): if self.isUnique and self.started_processes: if node_name: return {node_name: None} return {} #une transaction globale pour chaque demande global_transaction = Transaction() start_transition = self._startTransition startevent = start_transition.source # une transaction pour un evenement (pour l'instant c'est un evenement) sub_transaction = global_transaction.start_subtransaction( type='Find', initiator=self) start_workitems = startevent.start_process(sub_transaction) if node_name is None: start_workitems = {wi.node.__name__: wi for wi in start_workitems} return start_workitems for wi in start_workitems: if node_name == wi.node.__name__: return {node_name: wi} return {node_name: None} @property def started_processes(self): dace_catalog = find_catalog('dace') object_provides_index = dace_catalog['object_provides'] processid_index = dace_catalog['process_id'] query = object_provides_index.any((IProcess.__identifier__,)) & \ processid_index.eq(self.id) results = query.execute().all() processes = [p for p in results] #processes.sort() return processes