def deferred_default_hashtag_text(node, kw): """ If this is a reply to something else, the default value will contain the userid of the original poster + any hashtags used. """ context = kw["context"] request = kw["request"] output = u"" request_tag = request.GET.get("tag", None) if request_tag: if not HASHTAG_PATTERN.match(request_tag): request_tag = None # Blank it, since it's invalid! if IAgendaItem.providedBy(context): # This is a first post - don't add any hashtags or similar, # unless they're part of query string if request_tag: output += u"#%s" % request_tag return output # get creator of answered object creators = context.get_field_value("creators") if creators: output += u"@%s: " % creators[0] # get tags and make a string of them tags = list(context.get_tags([])) if request_tag and request_tag not in tags: tags.append(request_tag) for tag in tags: output += u" #%s" % tag return output
def get_ai_results(self): for obj in self.context.values(): if IAgendaItem.providedBy(obj): preprocess_tags = IPreprocessUserTags(obj) results = preprocess_tags.get_results() if results: yield obj, results
def _update_if_ai_parent(catalog, obj): """ Since AIs keep track of count of Poll, Proposal and Discussion objects. Only needed for add and remove. """ parent = getattr(obj, '__parent__', None) if IAgendaItem.providedBy(parent): reindex_object(catalog, parent)
def main(): parser = argparse.ArgumentParser() parser.add_argument("config_uri", help="Paster ini file to load settings from") parser.add_argument("path", help="from which path to clear likes (meeting or agenda item)") args = parser.parse_args() env = bootstrap(args.config_uri) root = env['root'] request = env['request'] context = traverse(root, args.path).get('context') if IMeeting.providedBy(context) or IAgendaItem.providedBy(context): print('Clearing likes on {}'.format(context.title)) path_query = query.Eq('path', args.path) cleared = False for type_name in ('Proposal', 'DiscussionPost'): count, docids = root.catalog.query(path_query & query.Eq('type_name', type_name)) response = input('Found {} {} on {}. Do you want to clear likes on these? (y/N) '.format( count, type_name, context.title).encode('utf8')) if response.lower() in ('y', 'yes', 'j', 'ja'): cleared = True for obj in request.resolve_docids(docids, perm=None): like = request.registry.getAdapter(obj, IUserTags, name='like') like.storage.clear() like._notify() if cleared: transaction.commit() env['closer']() else: print('Path does not match a meeting or agenda item')
def all_agenda_items_keys(node, kw): context = kw['context'] values = [] for (name, obj) in context.items(): if IAgendaItem.providedBy(obj): values.append(name) return values
def deferred_default_hashtag_text(node, kw): """ If this is a reply to something else, the default value will contain the userid of the original poster + any hashtags used. """ context = kw['context'] request = kw['request'] output = u"" request_tag = request.GET.get('tag', None) if request_tag: if not HASHTAG_PATTERN.match(request_tag): request_tag = None #Blank it, since it's invalid! if IAgendaItem.providedBy(context): #This is a first post - don't add any hashtags or similar, #unless they're part of query string if request_tag: output += u"#%s" % request_tag return output # get creator of answered object creators = context.get_field_value('creators') if creators: output += u"@%s: " % creators[0] # get tags and make a string of them tags = list(context.get_tags([])) if request_tag and request_tag not in tags: tags.append(request_tag) for tag in tags: output += u" #%s" % tag return output
def selectable_agenda_items_widget(node, kw): context = kw['context'] values = [] for (name, obj) in context.items(): if not IAgendaItem.providedBy(obj): continue values.append((name, obj.title)) return deform.widget.CheckboxChoiceWidget(values=values)
def speaker_list_controls_moderator(api, slists, context): assert IAgendaItem.providedBy(context) response = {} response['api'] = api response['context'] = context response['active_list'] = slists.get(slists.active_list_name) response['context_lists'] = slists.get_contextual_lists(context) return render("templates/speaker_list_controls_moderator.pt", response, request = api.request)
def render(self, context, request, view, **kwargs): if IAgendaItem.providedBy(context): query = "type_name == 'Poll' and path == '%s'" % resource_path(context) query += " and workflow_state in any(['ongoing', 'upcoming', 'closed'])" if request.is_moderator or view.catalog_query(query): url = request.resource_url(context, self.view_name) response = {'portlet': self.portlet, 'view': view, 'load_url': url} return render(self.template, response, request=request)
def get_docids_to_show(request, context, type_name, tags=(), limit=5, start_after=None, end_before=None): """ Helper method to fetch docids that would be a resonable batch to show. This is mostly to allow agenda views to load fast. - Fetch batch from the first unread docid so there will be no items skipped in case they were read from a tag view. - If batch contains less items than limit, insert items from previous. - start_after - remove everything before this docid and the value specified. - end_before - remove this value and everything after it. Result example: {'batch': [4, 5, 6], 'previous': [1, 2, 3], 'over_limit': [7, 8, 9], 'unread': [4, 5, 6, 7, 8, 9]} """ assert IAgendaItem.providedBy(context) query = Eq('path', resource_path(context)) query &= Eq('type_name', type_name) if tags: query &= Any('tags', list(tags)) read_names = request.get_read_names(context) unread_query = query & NotAny('__name__', set(read_names.get(request.authenticated_userid, []))) unread_docids = list(request.root.catalog.query(unread_query, sort_index='created')[1]) docids_pool = list(request.root.catalog.query(query, sort_index='created')[1]) if start_after and start_after in docids_pool: i = docids_pool.index(start_after) for docid in docids_pool[:i + 1]: if docid in unread_docids: unread_docids.remove(docid) docids_pool[0:i + 1] = [] if end_before and end_before in docids_pool: i = docids_pool.index(end_before) for docid in docids_pool[i:]: if docid in unread_docids: unread_docids.remove(docid) docids_pool[i:] = [] if limit: if unread_docids: first_pos = docids_pool.index(unread_docids[0]) batch = docids_pool[first_pos:first_pos + limit] over_limit = docids_pool[first_pos + limit:] previous = docids_pool[:first_pos] # Fill batch from last item of previous if batch is too small while previous and len(batch) < limit: batch.insert(0, previous.pop(-1)) else: batch = docids_pool[-limit:] previous = docids_pool[:-limit] over_limit = [] return {'batch': batch, 'previous': previous, 'over_limit': over_limit, 'unread': unread_docids} # no limit return {'batch': docids_pool, 'previous': [], 'over_limit': [], 'unread': unread_docids}
def render(self, context, request, view, **kwargs): if IAgendaItem.providedBy(context): query = {} tags = request.GET.getall('tag') if tags: query['tag'] = [x.lower() for x in tags] url = request.resource_url(context, self.view_name, query=query) response = {'portlet': self.portlet, 'view': view, 'load_url': url} return render(self.template, response, request=request)
def evolve(root): """ Remove old redis attrs """ for meeting in [x for x in root.values() if IMeeting.providedBy(x)]: if hasattr(meeting, '_read_names_counter'): delattr(meeting, '_read_names_counter') for ai in [x for x in meeting.values() if IAgendaItem.providedBy(x)]: if hasattr(ai, '_read_names'): delattr(ai, '_read_names')
def visible(self, context, request, view, **kwargs): if IAgendaItem.providedBy(context): if request.is_moderator: return True diff_text = IDiffText(context) paragraphs = diff_text.get_paragraphs() if paragraphs: return True return False
def add(self, key, value, parent = None): if not ISpeakerList.providedBy(value): raise TypeError("Only objects implementing ISpeakerList allowed") if parent is None: request = get_current_request() parent = request.context assert IAgendaItem.providedBy(parent) value.__parent__ = parent #To hack in traversal self.speaker_lists[key] = value return key
def projector_menu_link(context, request, va, **kw): """ Visible in the moderator menu, but doesn't work for the meeting root """ if IAgendaItem.providedBy(context): url = request.resource_url(request.meeting, '__projector__', anchor=context.__name__) else: url = request.resource_url(request.meeting, '__projector__') return """<li><a href="{url}" title="{title}">{title}</a></li>""".format( title=request.localizer.translate(va.title), url=url, )
def find_ais(root, found): for m in root.values(): if not IMeeting.providedBy(m): continue print "Processing: /%s" % m.__name__ for ai in m.values(): if not IAgendaItem.providedBy(ai) or ai.uid not in found: continue mark_read(ai, found.pop(ai.uid)) if not found: return
def render(self, context, request, view, **kwargs): if request.meeting: ai_name = IAgendaItem.providedBy( context) and context.__name__ or '' response = { 'title': self.title, 'portlet': self.portlet, 'view': view, 'load_url': self.load_url(request, ai_name) } return render(self.tpl, response, request=request)
def minutes(self): """ Show an overview of the meeting activities. Should work as a template for minutes. """ if self.request.meeting.get_workflow_state() != 'closed': msg = _(u"meeting_not_closed_minutes_incomplete_notice", default = u"This meeting hasn't closed yet so these minutes won't be complete") self.flash_messages.add(msg) #Add agenda item objects to response results = [] for obj in self.context.values(): if IAgendaItem.providedBy(obj) and self.request.has_permission(security.VIEW, obj): results.append(obj) return {'agenda_items': results}
def set_initial_order(obj, event): """ Sets the initial order of the agenda item """ meeting = find_interface(obj, IMeeting) assert meeting order = 0 for ai in meeting.values(): if IAgendaItem.providedBy(ai): if ai.get_field_value('order') >= order: order = ai.get_field_value('order')+1 obj.set_field_appstruct({'order': order})
def render(self, context, request, view, **kwargs): if IAgendaItem.providedBy(context): pns = IParticipantNumbers(request.meeting, None) pn = None if pns: pn = pns.userid_to_number.get(request.authenticated_userid, None) update_interv = request.speaker_lists.settings.get('user_update_interval', 5) return render(self.tpl, {'portlet': self.portlet, 'view': view, 'user_update_interval': update_interv, 'pn': pn}, request=request)
def save_success(self, appstruct): from_meeting = self.request.root[appstruct['meeting_name']] reset_wf = appstruct['all_props_published'] only_copy_prop_states = appstruct['only_copy_prop_states'] copy_types = appstruct['copy_types'] assert IMeeting.providedBy(from_meeting) counter = 0 for ai in from_meeting.values(): if not IAgendaItem.providedBy(ai): continue counter += copy_ai(self.context, ai, reset_wf=reset_wf, only_copy_prop_states=only_copy_prop_states, copy_types=copy_types) self.flash_messages.add(_("Copied ${num} objects", mapping={'num': counter})) return HTTPFound(location=self.request.resource_url(self.context))
def render(self, context, request, view, **kwargs): if IAgendaItem.providedBy(context): query = "type_name == 'Poll' and path == '%s'" % resource_path( context) query += " and workflow_state in any(['ongoing', 'upcoming', 'closed'])" if request.is_moderator or view.catalog_query(query): url = request.resource_url(context, self.view_name) response = { 'portlet': self.portlet, 'view': view, 'load_url': url } return render(self.template, response, request=request)
def evolve(root): """ Migrate all old unread markers that were stored in the catalog. """ if '__name__' not in root.catalog: raise KeyError("__name__ index must be in catalog before this migration." "Run catalog update first.") #We only need to care about meetings for m in root.values(): if not IMeeting.providedBy(m): continue users = set(root.local_roles) | set(m.local_roles) for ai in m.values(): if not IAgendaItem.providedBy(ai): continue convert_ai(ai, users) print "Processed '%s' for %s users" % (m.__name__, len(users))
def system_users_in_add_schema(schema, event): """ Inject a choice so add a proposal as another user if: - The current user is a moderator - System users are set on the meeting """ #The context check here is essentially a guess if this is an add-view. if IAgendaItem.providedBy( event.context) and event.request.has_permission(MODERATE_MEETING): system_userids = _get_other_system_userids(event.request) if system_userids: schema.add( colander.SchemaNode(colander.Set(), name='creator', title=_("Add as"), widget=add_as_system_user_widget, validator=current_user_or_system_users, preparer=to_tuple, default=current_userid_as_tuple, missing=current_userid_as_tuple))
def _get_sibbling_ai(self, pos): meeting = self.request.meeting m_order = tuple(meeting.order) ai_count = len(m_order) try: this_pos = meeting.order.index(self.context.__name__) except ValueError: return #only try a few times - we don't want to iterate through the whole meeting # if there's lots of hidden Agenda items. for obj_pos in range(this_pos+pos, this_pos+(pos*5), pos): if obj_pos < 0 or obj_pos > ai_count: return try: obj = meeting[m_order[obj_pos]] except (KeyError, IndexError): return if not IAgendaItem.providedBy(obj): continue if self.request.has_permission(VIEW, obj): return obj
def _reorder_ais(meeting): order_priority = {} #Order will be blank so we must fetch the keys from data curr_keys = set(meeting.data.keys()) if curr_keys != set(meeting.keys()): meeting.order = meeting.data.keys() for k in curr_keys: order_priority[k] = len(curr_keys) for ai in meeting.values(): if not IAgendaItem.providedBy(ai): continue if 'order' in ai.field_storage: order_priority[ai.__name__] = ai.field_storage.pop('order') def _sorter(key): return order_priority[key] order = sorted(curr_keys, key=_sorter) meeting.order = order for obj in find_all_db_objects(meeting): cataloger = ICataloger(obj, None) if cataloger: cataloger.index_object()
def main(): parser = argparse.ArgumentParser() parser.add_argument("config_uri", help="Paster ini file to load settings from") parser.add_argument( "path", help="from which path to clear likes (meeting or agenda item)") args = parser.parse_args() env = bootstrap(args.config_uri) root = env['root'] request = env['request'] context = traverse(root, args.path).get('context') if IMeeting.providedBy(context) or IAgendaItem.providedBy(context): print('Clearing likes on {}'.format(context.title)) path_query = query.Eq('path', args.path) cleared = False for type_name in ('Proposal', 'DiscussionPost'): count, docids = root.catalog.query( path_query & query.Eq('type_name', type_name)) response = input( 'Found {} {} on {}. Do you want to clear likes on these? (y/N) ' .format(count, type_name, context.title).encode('utf8')) if response.lower() in ('y', 'yes', 'j', 'ja'): cleared = True for obj in request.resolve_docids(docids, perm=None): like = request.registry.getAdapter(obj, IUserTags, name='like') like.storage.clear() like._notify() if cleared: transaction.commit() env['closer']() else: print('Path does not match a meeting or agenda item')
def evolve(root): """ Migrate unread to redis """ try: from fakeredis import FakeStrictRedis maybe_fr = True except ImportError: maybe_fr = False request = get_current_request() if maybe_fr: if isinstance(request.redis_conn, FakeStrictRedis): raise Exception( "Your redis connection is a FakeRedis instance. " "All migrated data would be thrown away! " "Please set voteit.redis_url in your paster.ini file.") for meeting in [x for x in root.values() if IMeeting.providedBy(x)]: if hasattr(meeting, '_read_names_counter'): delattr(meeting, '_read_names_counter') for ai in [x for x in meeting.values() if IAgendaItem.providedBy(x)]: if not hasattr(ai, '_read_names'): continue read_names = request.get_read_names(ai) for (userid, names) in ai._read_names.items(): read_names.mark_read(names, userid)
def visible(self, context, request, view, **kwargs): # These will all be visible regardless return IAgendaItem.providedBy(context)
class ManageAgendaItemsView(BaseView): def __call__(self): post = self.request.POST if 'cancel' in self.request.POST: url = self.request.resource_url(self.context) return HTTPFound(location=url) if 'change' in post: state_id = self.request.POST['state_id'] block_proposals = self.request.POST.get('block_proposals', None) block_proposals = _BLOCK_VALS.get(block_proposals, None) block_discussion = self.request.POST.get('block_discussion', None) block_discussion = _BLOCK_VALS.get(block_discussion, None) controls = self.request.POST.items() agenda_items = [] for (k, v) in controls: if k == 'ais': agenda_items.append(self.context[v]) output_msg = "" translate = self.request.localizer.translate #WF state change if state_id: states_changed = 0 for ai in agenda_items: try: ai.set_workflow_state(self.request, state_id) states_changed += 1 except WorkflowError, e: self.flash_messages.add(_( 'Unable to change state on ${title}: ${error}', mapping={ 'title': ai.title, 'error': e }), type='danger') if states_changed: output_msg += translate( _("${num} changed state", mapping={'num': states_changed})) output_msg += "<br/>" #Block states if block_proposals != None or block_discussion != None: blocked = 0 for ai in agenda_items: blocked += 1 if block_proposals != None: ai.set_field_value('proposal_block', block_proposals) if block_discussion != None: ai.set_field_value('discussion_block', block_discussion) if blocked: output_msg += translate( _("Changing block state for ${num} agenda items.", mapping={'num': blocked})) if output_msg: self.flash_messages.add(output_msg, type='success') else: self.flash_messages.add(_('Nothing updated'), type='warning') return HTTPFound(location=self.request.resource_url( self.context, 'manage_agenda')) state_info = _dummy_agenda_item.workflow.state_info(None, self.request) tstring = _ def _translated_state_title(state): for info in state_info: if info['name'] == state: return tstring(info['title']) return state response = {} response['translated_state_title'] = _translated_state_title response['find_resource'] = find_resource response['states'] = states = ('ongoing', 'upcoming', 'closed', 'private') response['ais'] = ais = {} for state in states: ais[state] = [] for obj in self.context.values(): if IAgendaItem.providedBy(obj): ais[obj.get_workflow_state()].append(obj) return response
def get_ai(self): if self.ai_name in self.context: ai = self.context[self.ai_name] if IAgendaItem.providedBy(ai): return ai
def get_docids_to_show(request, context, type_name, tags=(), limit=5, start_after=None, end_before=None): """ Helper method to fetch docids that would be a resonable batch to show. This is mostly to allow agenda views to load fast. - Fetch batch from the first unread docid so there will be no items skipped in case they were read from a tag view. - If batch contains less items than limit, insert items from previous. - start_after - remove everything before this docid and the value specified. - end_before - remove this value and everything after it. Result example: {'batch': [4, 5, 6], 'previous': [1, 2, 3], 'over_limit': [7, 8, 9], 'unread': [4, 5, 6, 7, 8, 9]} """ assert IAgendaItem.providedBy(context) query = Eq('path', resource_path(context)) query &= Eq('type_name', type_name) if tags: query &= Any('tags', list(tags)) read_names = request.get_read_names(context) unread_query = query & NotAny( '__name__', set(read_names.get(request.authenticated_userid, []))) unread_docids = list( request.root.catalog.query(unread_query, sort_index='created')[1]) docids_pool = list( request.root.catalog.query(query, sort_index='created')[1]) if start_after and start_after in docids_pool: i = docids_pool.index(start_after) for docid in docids_pool[:i + 1]: if docid in unread_docids: unread_docids.remove(docid) docids_pool[0:i + 1] = [] if end_before and end_before in docids_pool: i = docids_pool.index(end_before) for docid in docids_pool[i:]: if docid in unread_docids: unread_docids.remove(docid) docids_pool[i:] = [] if limit: if unread_docids: first_pos = docids_pool.index(unread_docids[0]) batch = docids_pool[first_pos:first_pos + limit] over_limit = docids_pool[first_pos + limit:] previous = docids_pool[:first_pos] # Fill batch from last item of previous if batch is too small while previous and len(batch) < limit: batch.insert(0, previous.pop(-1)) else: batch = docids_pool[-limit:] previous = docids_pool[:-limit] over_limit = [] return { 'batch': batch, 'previous': previous, 'over_limit': over_limit, 'unread': unread_docids } # no limit return { 'batch': docids_pool, 'previous': [], 'over_limit': [], 'unread': unread_docids }
def get_read_names(request, context): assert IAgendaItem.providedBy(context) return request.registry.queryMultiAdapter([context, request], IReadNames)
def moderator_context_delete(context, request, va, **kw): """ This is already a part of the Arches context menu, so it shouldn't be shown in meetings or agenda items. """ if IAgendaItem.providedBy(context) or IMeeting.providedBy(context): return return moderator_context_action(context, request, va, **kw)