def bootstrap_voteit(echo=True): """ Bootstrap site root. Will add: - Site root - Agenda template folder - Users folder - An administrative user with login: admin and pass: admin """ if echo: print "Bootstrapping site - creating 'admin' user with password 'admin'" #Add root root = createContent('Root', title = _(u"VoteIT"), creators = ['admin']) root.set_field_value('footer', u'<a href="http://www.voteit.se">www.voteit.se</a> — ' u'<a href="http://manual.voteit.se">User and developer manual</a> — ' u'<a href="https://github.com/VoteIT">Source code and bugtracker</a>') #Add templates if available try: root['agenda_templates'] = createContent('AgendaTemplates', title = _(u"Agenda templates"), creators = ['admin']) except KeyError: pass #For tests etc #Add users folder root['users'] = createContent('Users', title = _(u"Registered users"), creators = ['admin']) users = root.users #Add user admin - note that creators also set owner, which is important for changing password admin = createContent('User', password = '******', creators = ['admin'], first_name = _(u'VoteIT'), last_name = _(u'Administrator')) users['admin'] = admin #Add admin to group managers root.add_groups('admin', [ROLE_ADMIN]) return root
def add_form(self): post = self.request.POST if 'cancel' in post: self.api.flash_messages.add(_(u"Canceled")) url = resource_url(self.context, self.request) return HTTPFound(location=url) schema = createSchema('AddUserSchema') add_csrf_token(self.context, self.request, schema) schema = schema.bind(context=self.context, request=self.request, api = self.api) form = Form(schema, buttons=(button_add, button_cancel)) self.api.register_form_resources(form) if 'add' in post: controls = post.items() try: #appstruct is deforms convention. It will be the submitted data in a dict. appstruct = form.validate(controls) except ValidationFailure, e: self.response['form'] = e.render() return self.response #Userid and name should be consistent name = appstruct['userid'] del appstruct['userid'] #creators takes care of setting the role owner as well as adding it to creators attr. obj = createContent('User', creators=[name], **appstruct) self.context[name] = obj self.api.flash_messages.add(_(u"Successfully added")) url = resource_url(self.context, self.request) return HTTPFound(location=url)
def order_agenda_items(self): self.response['title'] = _( u"order_agenda_items_view_title", default=u"Drag and drop agenda items to reorder") post = self.request.POST if 'cancel' in self.request.POST: url = resource_url(self.context, self.request) return HTTPFound(location=url) if 'save' in post: controls = self.request.POST.items() ais = [] order = 0 for (k, v) in controls: if k == 'agenda_items': ai = self.context[v] ai.set_field_appstruct({'order': order}) order += 1 self.api.flash_messages.add(_('Order updated')) context_path = resource_path(self.context) query = dict( path=context_path, content_type='AgendaItem', sort_index='order', ) self.response['brains'] = self.api.get_metadata_for_query(**query) return self.response
def add_permission(self): if ISiteRoot.providedBy(self.context): self.response['title'] = _(u"Add permission") post = self.request.POST if 'cancel' in post: url = resource_url(self.context, self.request) return HTTPFound(location=url) schema = createSchema('SinglePermissionSchema') add_csrf_token(self.context, self.request, schema) schema = schema.bind(context=self.context, request=self.request, api=self.api) form = Form(schema, buttons=(button_add, button_cancel)) self.api.register_form_resources(form) if IMeeting.providedBy(self.context): self.response['tabs'] = self.api.render_single_view_component( self.context, self.request, 'tabs', 'manage_tickets') if 'add' in post: controls = post.items() try: appstruct = form.validate(controls) except ValidationFailure, e: self.response['form'] = e.render() return self.response #Set permissions self.context.set_groups(appstruct['userid'], appstruct['groups'], event=True) msg = _(u"Added permssion for user ${userid}", mapping={'userid': appstruct['userid']}) self.api.flash_messages.add(msg) url = resource_url(self.context, self.request) return HTTPFound(location=url)
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'))
def userid_node(): return colander.SchemaNode(colander.String(), title = _(u"UserID"), description = _('userid_description', default=u"Used as a nickname, in @-links and as a unique id. " u"You can't change this later. OK characters are: a-z, 0-9, '.', '-', '_'."), validator=deferred_new_userid_validator,)
def group_form(self): if IMeeting.providedBy(self.context): self.response['title'] = _(u"Edit permissions") else: self.response['title'] = _(u"Root permissions") post = self.request.POST if 'cancel' in post: url = resource_url(self.context, self.request) return HTTPFound(location=url) schema = createSchema('PermissionsSchema') add_csrf_token(self.context, self.request, schema) schema = schema.bind(context=self.context, request=self.request, api=self.api) form = Form(schema, buttons=('save', 'cancel')) self.api.register_form_resources(form) if 'save' in post: controls = post.items() try: appstruct = form.validate(controls) except ValidationFailure, e: self.response['form'] = e.render() return self.response #Set permissions self.context.set_security(appstruct['userids_and_groups']) url = resource_url(self.context, self.request) return HTTPFound(location=url)
def root_schema_adjustments(schema, event): schema.add( colander.SchemaNode( colander.String(), missing="", name='support_email', title=_(u"Support email for this site"), description=_( u"support_email_schema_desription", default=u"This email will receive mail sent through the support " "request form visible in the help menu."), validator=colander.Email(), )) schema.add( colander.SchemaNode( colander.String(), missing="", name='body', title=_("Main body text"), description=_( "main_body_desc", default= "This is the first page of VoteIT. Describe your instance here." ), widget=deform.widget.RichTextWidget(), validator=richtext_validator, ))
def agenda_template_select(self): #FIXME: Should this be a migrate script? try: agenda_templates = self.api.root['agenda_templates'] except KeyError: # pragma: no coverage obj = createContent('AgendaTemplates', title = _(u"Agenda templates"), creators = ['admin']) agenda_templates = self.api.root['agenda_templates'] = obj get = self.request.GET if 'apply' in get: template_name = get['apply'] if template_name in agenda_templates: template = agenda_templates[template_name] template.populate_meeting(self.context) msg = _(u"agenda_template_apply_template_success", default = u"Selected template applied to meeting.") self.api.flash_messages.add(msg) return HTTPFound(location = resource_url(self.context, self.request)) else: err_msg = _(u"agenda_template_apply_invalid_template", default = u"No template named ${template} could be found.", mapping = {'template': template_name}) self.api.flash_messages.add(err_msg, type="error") self.response['agenda_templates'] = agenda_templates return self.response
def render_result(self, view): votes = [x['uid']['proposal'] for x in self.context.poll_result] novotes = set(self.context.proposal_uids) - set(votes) translate = view.request.localizer.translate vote_singular = translate(_("vote_singular", default = "Vote")) vote_plural = translate(_("vote_plural", default = "Votes")) def _vote_text(count): return view.request.localizer.pluralize(vote_singular, vote_plural, count) results = [] #Adjust result layout for res in tuple(self.context.poll_result): results.append({'uid': res['uid']['proposal'], 'count': res['count'], 'num': res['num'], 'perc': int(round(res['num'] * 100, 0))}) for uid in novotes: results.append({'uid': uid, 'count': 0, 'num': 0, 'perc': 0}) response = {} response['results'] = results #response['novotes'] = novotes response['vote_text'] = _vote_text response['total'] = sum([x[1] for x in self.context.ballots]) proposals = {} for prop in self.context.get_proposal_objects(): proposals[prop.uid] = prop response['proposals'] = proposals return render('templates/majority_poll.pt', response, request = view.request)
def configure_access_policy(self): access_policy_name = self.context.get_field_value('access_policy', 'invite_only') access_policy = self.request.registry.queryAdapter(self.context, IAccessPolicy, name = access_policy_name) if not access_policy: err_msg = _(u"access_policy_not_found_moderator", default = u"""Can't find an access policy with the id '${policy}'. This might mean that the registered access type for this meeting doesn't exist anylonger. Please change access policy.""", mapping = {'policy': access_policy_name}) self.api.flash_messages.add(err_msg, type="error") url = self.request.resource_url(self.api.meeting, 'access_policy') return HTTPFound(location=url) form = access_policy.config_form(self.api) post = self.request.POST if 'save' in post: controls = post.items() try: appstruct = form.validate(controls) except deform.ValidationFailure, e: self.response['form'] = e.render() return self.response self.context.set_field_appstruct(appstruct) self.api.flash_messages.add(_(u"Saved")) url = self.request.resource_url(self.api.meeting) return HTTPFound(location=url)
class CondorcetLooser(Criteria): title = _("Condorcet looser") help = _( "criteria_cl_help", default= "The looser must loose against every other candidate in a pairwise comparison.", )
class CloneProof(Criteria): title = _("Clone proof") help = _( "criteria_cp_help", default= "The winner must not change due to strategic nomination, for instance that a similar candidate runs.", )
class CondorcetWinner(Criteria): title = _("Condorcet winner") help = _( "criteria_cw_help", default= "The winner must beat every other candidate in a pairwise comparison.", )
class MajorityLooser(Criteria): title = _("Majority looser") help = _( "criteria_ml_help", default= "If a majority of voters do not want this, it shouldn't be able to win.", )
class MajorityWinner(Criteria): title = _("Majority winner") help = _( "criteria_mw_help", default= "A majority (more than 50%) must prefer this, otherwise it shouldn't be able to win.", )
def _results_ts(count): """ Note about the odd syntax: pluralize returns unicode, so it won't be translated. Hence it needs to be converted back to a translation string. """ return _(self.api.pluralize(_(u"item"), _(u"items"), count))
def edit_form(self): """ For configuring polls that haven't started yet. """ schema_name = self.api.get_schema_name(self.context.content_type, 'edit') schema = createSchema(schema_name, after_bind=poll_schema_after_bind) add_csrf_token(self.context, self.request, schema) schema = schema.bind(context=self.context, request=self.request, api = self.api) form = Form(schema, buttons=(button_update, button_cancel)) self.api.register_form_resources(form) post = self.request.POST if self.request.method == 'POST': if 'update' in post: controls = post.items() try: #appstruct is deforms convention. It will be the submitted data in a dict. appstruct = form.validate(controls) except ValidationFailure, e: self.response['form'] = e.render() return self.response removed_uids = set(self.context.proposal_uids) - set(appstruct['proposals']) if removed_uids: #Adjust removed proposals back to published state, if they're locked for uid in removed_uids: prop = self.context.get_proposal_by_uid(uid) if prop.get_workflow_state() == 'voting': prop.set_workflow_state(self.request, u'published') updated = self.context.set_field_appstruct(appstruct) if updated: self.api.flash_messages.add(_(u"Successfully updated")) else: self.api.flash_messages.add(_(u"Nothing changed")) if 'cancel' in post: self.api.flash_messages.add(_(u"Canceled")) url = self.request.resource_url(self.context.__parent__, anchor = self.context.uid) return HTTPFound(location = url)
def bootstrap_voteit(echo=True): """ Bootstrap site root. Will add: - Site root - Agenda template folder - Users folder - An administrative user with login: admin and pass: admin """ if echo: print "Bootstrapping site - creating 'admin' user with password 'admin'" #Add root root = createContent('SiteRoot', title=_(u"VoteIT"), creators=['admin']) root.set_field_value( 'footer', u'<a href="http://www.voteit.se">www.voteit.se</a> — ' u'<a href="http://manual.voteit.se">User and developer manual</a> — ' u'<a href="https://github.com/VoteIT">Source code and bugtracker</a>') #Add users folder root['agenda_templates'] = createContent('AgendaTemplates', title=_(u"Agenda templates"), creators=['admin']) #Add users folder root['users'] = createContent('Users', title=_(u"Registered users"), creators=['admin']) users = root.users #Add user admin - note that creators also set owner, which is important for changing password admin = createContent('User', password='******', creators=['admin'], first_name=_(u'VoteIT'), last_name=_(u'Administrator')) users['admin'] = admin #Add admin to group managers root.add_groups('admin', [ROLE_ADMIN]) return root
class ImmediateAP(AccessPolicy): """ Grant access for specific permissions immediately if a user requests it. No moderator approval requred. This is for very public meetings. """ name = 'public' title = _(u"public_access_title", default=u"Public access") description = _( u"public_access_description", default= u"Users will be granted the permissions you select without prior moderator approval. This is for public meetings." ) configurable = True def schema(self, api): return colander.Schema( title=_(u"Would you like to participate?"), description=_( u"Clicking request access will grant you access right away!")) def handle_success(self, api, appstruct): rolesdict = dict(security.STANDARD_ROLES) roles = self.context.get_field_value('immediate_access_grant_roles') self.context.add_groups(api.userid, roles) api.flash_messages.add(_(u"Access granted - welcome!")) def config_schema(self, api): return createSchema('ImmediateAPConfigSchema')
def render_result(self, view): votes = [x['uid']['proposal'] for x in self.context.poll_result] novotes = set(self.context.proposal_uids) - set(votes) translate = view.request.localizer.translate vote_singular = translate(_("vote_singular", default="Vote")) vote_plural = translate(_("vote_plural", default="Votes")) def _vote_text(count): return view.request.localizer.pluralize(vote_singular, vote_plural, count) results = [] #Adjust result layout for res in tuple(self.context.poll_result): results.append({ 'uid': res['uid']['proposal'], 'count': res['count'], 'num': res['num'], 'perc': int(round(res['num'] * 100, 0)) }) for uid in novotes: results.append({'uid': uid, 'count': 0, 'num': 0, 'perc': 0}) response = {} response['results'] = results #response['novotes'] = novotes response['vote_text'] = _vote_text response['total'] = sum([x[1] for x in self.context.ballots]) proposals = {} for prop in self.context.get_proposal_objects(): proposals[prop.uid] = prop response['proposals'] = proposals return render('templates/majority_poll.pt', response, request=view.request)
def adjust_proposal_states(self, uid_states, request = None): assert isinstance(uid_states, dict) if request is None: request = get_current_request() changed = [] change_error = [] for (uid, state) in uid_states.items(): if uid not in self.proposal_uids: raise ValueError("The poll plugins close() method returned a uid that doesn't exist in this poll.") proposal = self.get_proposal_by_uid(uid) #Adjust state? prop_wf_state = proposal.get_workflow_state() if prop_wf_state != state: try: proposal.set_workflow_state(request, state) changed.append(proposal) except WorkflowError: change_error.append(proposal) fm = get_flash_messages(request) msg = _('poll_closed_info', default = "Poll has now closed. ${num} proposal(s) are now set in another state due to the outcome of the poll.", mapping = {'num': len(changed)}) fm.add(msg) if change_error: msg = _('poll_closed_proposal_wf_change_error', default = "Couldn't adjust the state of the following proposal(s): '${props}'. " "You may wish to review them manually.", mapping = {'props': "', '".join(prop.aid for prop in change_error)}) fm.add(msg, type = 'danger')
def sort(self): self.response['title'] = _(u"order_agenda_template_view_title", default = u"Drag and drop agenda items to reorder") post = self.request.POST if 'cancel' in self.request.POST: url = resource_url(self.context, self.request) return HTTPFound(location = url) if 'save' in post: controls = self.request.POST.items() ais = self.context.get_field_value('agenda_items') order = 0 agenda_items = [] for (k, v) in controls: if k == 'agenda_items': ai = ais[int(v)] ai['order'] = order order += 1 agenda_items.append(ai) self.context.set_field_value('agenda_items', agenda_items) self.api.flash_messages.add(_(u'Order updated')) url = resource_url(self.context, self.request) return HTTPFound(location = url) return self.response
def ticket_claim(self): """ After login or registration, redirect back here, where information about the ticket will be displayed, and a confirmation that you want to use the ticket for the current user. While we use a regular deform form, it's not ment to be displayed or handle any validation. """ if not self.api.userid: raise HTTPForbidden("Direct access to this view for unauthorized users not allowed.") schema = createSchema('ClaimTicketSchema', validator = deferred_token_form_validator) schema = schema.bind(context=self.context, request=self.request, api = self.api) form = deform.Form(schema, buttons=(button_add, button_cancel)) if self.request.GET.get('claim'): controls = self.request.params.items() try: appstruct = form.validate(controls) except deform.ValidationFailure, e: msg = _(u"ticket_validation_fail", default = u"Ticket validation failed. Either the ticket doesn't exist, was already used or the url used improperly. " u"If you need help, please contact the moderator that invited you to this meeting.") self.api.flash_messages.add(msg, type = 'error') url = self.request.resource_url(self.api.root) return HTTPFound(location = url) #Everything in order, claim ticket ticket = self.context.invite_tickets[appstruct['email']] ticket.claim(self.request) self.api.flash_messages.add(_(u"You've been granted access to the meeting. Welcome!")) url = self.request.resource_url(self.context) return HTTPFound(location=url)
def render_result(self, view): votes = [x["uid"]["proposal"] for x in self.context.poll_result] novotes = set(self.context.proposal_uids) - set(votes) translate = view.request.localizer.translate vote_singular = translate(_("vote_singular", default="Vote")) vote_plural = translate(_("vote_plural", default="Votes")) def _vote_text(count): return view.request.localizer.pluralize(vote_singular, vote_plural, count) results = [] # Adjust result layout for res in tuple(self.context.poll_result): results.append({ "uid": res["uid"]["proposal"], "count": res["count"], "num": res["num"], "perc": int(round(res["num"] * 100, 0)), }) for uid in novotes: results.append({"uid": uid, "count": 0, "num": 0, "perc": 0}) response = {} response["results"] = results # response['novotes'] = novotes response["vote_text"] = _vote_text response["total"] = sum([x[1] for x in self.context.ballots]) proposals = {} for prop in self.context.get_proposal_objects(): proposals[prop.uid] = prop response["proposals"] = proposals return render("templates/majority_poll.pt", response, request=view.request)
def vote_success(self, appstruct): #Just in case the form rendered before the poll closed if not self.can_vote: raise HTTPForbidden(_("You're not allowed to vote")) Vote = self.poll_plugin.get_vote_class() if not IVote.implementedBy(Vote): raise TypeError("Poll plugins method get_vote_class returned something that didn't implement IVote.") appstruct.pop('csrf_token', None) userid = self.api.userid if userid in self.context: vote = self.context[userid] assert IVote.providedBy(vote) vote.set_vote_data(appstruct) else: vote = Vote(creators = [userid]) #We don't need to send events here, since object added will take care of that vote.set_vote_data(appstruct, notify = False) #To fire events after set_vote_data is done self.context[userid] = vote success_msg = _(u"Your vote has been registered!") if self.request.is_xhr: self.response['success_msg'] = success_msg return Response(render("templates/snippets/vote_success.pt", self.response, request = self.request)) self.api.flash_messages.add(success_msg) url = self.request.resource_url(self.context.__parent__, anchor = self.context.uid) return HTTPFound(location = url)
def __call__(self): tags = self.request.params.getall('tag') response = {'tags': tags} if tags: trans = self.request.localizer.translate filter_msg = """%s <a href="%s" data-load-agenda-item="#content" class="btn btn-default btn-xs" data-ai-name="%s"> %s </a> """ % ( trans(_("Filter active, showing ${num} tag(s)", mapping={'num': len(tags)})), self.request.clear_tags_url(self.context), self.context.__name__, trans(_("Show all")), ) filter_msg = filter_msg.replace('\n','') response['filter_msg'] = filter_msg # 200 is a default, but None removes the setting, hence this collapsible_limit = self.context.collapsible_limit if collapsible_limit is None: collapsible_limit = 200 if collapsible_limit == 0: collapsible_limit = None response['collapsible_limit'] = collapsible_limit return response
def add_success(self, appstruct): emails = appstruct['emails'].splitlines() roles = appstruct['roles'] added = 0 rejected = 0 for email in emails: result = self.context.add_invite_ticket(email, roles, sent_by = self.request.authenticated_userid) if result: added += 1 else: rejected += 1 if not rejected: msg = _('added_tickets_text', default = "Successfully added ${added} invites", mapping={'added': added}) elif not added: msg = _('no_tickets_added', default = "No tickets added - all you specified probably exist already. " "(Proccessed ${rejected})", mapping = {'rejected': rejected}) self.flash_messages.add(msg, type = 'warning', auto_destruct = False) url = self.request.resource_url(self.context, 'add_tickets') return HTTPFound(location = url) else: msg = _('added_tickets_text_some_rejected', default = "Successfully added ${added} invites but discarded ${rejected} " "since they already existed or were already used.", mapping={'added': added, 'rejected': rejected}) self.flash_messages.add(msg) self.request.session['send_tickets.emails'] = emails self.request.session['send_tickets.message'] = appstruct['message'] url = self.request.resource_url(self.context, 'send_tickets') return HTTPFound(location = url)
def ticket_claim(self): """ After login or registration, redirect back here, where information about the ticket will be displayed, and a confirmation that you want to use the ticket for the current user. While we use a regular deform form, it's not ment to be displayed or handle any validation. """ if not self.request.authenticated_userid: raise HTTPForbidden("Direct access to this view for unauthorized users not allowed.") email = self.request.GET.get('email', '') ticket = self.context.invite_tickets.get(email, None) if ticket and ticket.closed != None: msg = _("This ticket has already been used.") self.flash_messages.add(msg, type = 'danger', auto_destruct = True, require_commit = False) return HTTPFound(location = self.request.resource_url(self.context)) schema = get_content_schemas(self.request.registry)['Meeting']['claim_ticket']() schema = schema.bind(context = self.context, request = self.request, view = self) form = deform.Form(schema, buttons = (button_add, button_cancel,)) if self.request.GET.get('claim'): controls = self.request.params.items() try: appstruct = form.validate(controls) except deform.ValidationFailure, e: msg = _("ticket_validation_fail", default = "Ticket validation failed. Either the " "ticket doesn't exist, was already used or the url used improperly. " "If you need help, please contact the moderator that invited you to this meeting.") self.flash_messages.add(msg, type = 'danger', auto_destruct = False, require_commit = False) url = self.request.resource_url(self.root) return HTTPFound(location = url) #Everything in order, claim ticket ticket = self.context.invite_tickets[appstruct['email']] claim_ticket(ticket, self.request, self.request.authenticated_userid) self.flash_messages.add(_(u"You've been granted access to the meeting. Welcome!")) url = self.request.resource_url(self.context) return HTTPFound(location=url)
class ProposalSettingsSchema(colander.Schema): hide_proposal_states = colander.SchemaNode( colander.Set(), title=_("Hide proposal states"), description=_("hide_proposal_states_description", default="Proposals in these states will be hidden by " "default but can be shown by pressing " "the link below the other proposals. They're not " "by any means invisible to participants."), widget=proposal_states_widget, default=('retracted', 'denied', 'unhandled'), ) system_userids = colander.SchemaNode( colander.List(), widget=MeetingUserReferenceWidget(multiple=True), validator=existing_userids, title=_("System user accounts"), description=_("system_userids_description", default="Must be an existing userid. " "If they're added here, moderators can use them " "to add proposals in their name. " "It's good practice to add things like 'propositions', " "'board' or similar."), missing=(), ) proposal_id_method = colander.SchemaNode( colander.String(), title=_("Proposal naming method"), widget=proposal_naming_widget, missing="", )
class GravatarProfileImagePlugin(object): name = u"gravatar_profile_image" title = _("Gravatar") description = _( "profile_gravatar_explanation", default= 'Profile image from <a href="http://www.gravatar.com" target="_blank">Gravatar network</a>. ' "It's taken from your current email address. If you want to change the picture, simply go to " "the Gravatar site and change your picture for the email you use in VoteIT.", ) def __init__(self, context): self.context = context def url(self, size, request): url = "https://secure.gravatar.com/avatar/" email = self.context.get_field_value("email", "").strip().lower() if email: url += md5(email).hexdigest() url += "?s=%s" % size gt = request.root.site_settings.get( "gravatar_default_type", request.registry.settings.get("voteit.gravatar_default", "robohash"), ) url += "&d=%s" % gt return url def is_valid_for_user(self): return True
def request_password(self): schema = createSchema('RequestNewPasswordSchema').bind(context=self.context, request=self.request) form = Form(schema, buttons=(button_request, button_cancel)) self.api.register_form_resources(form) #Handle submitted information if 'request' in self.request.POST: controls = self.request.POST.items() try: #appstruct is deforms convention. It will be the submitted data in a dict. appstruct = form.validate(controls) except ValidationFailure, e: self.response['form'] = e.render() return self.response userid_or_email = appstruct['userid_or_email'] #userid here can be either an email address or a login name if '@' in userid_or_email: #assume email user = self.context['users'].get_user_by_email(userid_or_email) else: user = self.context['users'].get(userid_or_email) if IUser.providedBy(user): user.new_request_password_token(self.request) self.api.flash_messages.add(_('Email sent.')) url = resource_url(self.api.root, self.request) return HTTPFound(location = url) self.api.flash_messages.add(_('Username or email not found.'), type='error')
def add_success(self, appstruct): userid = appstruct['userid'] roles = appstruct['roles'] if roles and security.ROLE_VIEWER not in roles: roles.add(security.ROLE_VIEWER) old_roles = self.context.local_roles.get(userid, set()) if old_roles: new_roles = roles - old_roles if new_roles: trans = self.request.localizer.translate role_titles = [] for role_name in new_roles: role = self.request.registry.roles.get(role_name) role_titles.append(trans(role.title)) msg = _("new_roles_appended_notice", default = "User was already a part of this meeting, " "but these new roles were added: ${roles}", mapping = {'roles': ", ".join(role_titles)}) self.flash_messages.add(msg, type = 'warning') else: self.flash_messages.add(_("No new roles added - user already had all of them.")) else: #Userid wasn't registered in this meeting self.flash_messages.add(self.default_success, type = "success") self.context.local_roles.add(appstruct['userid'], roles) return HTTPFound(location = self.request.resource_url(self.context, 'add_userid'))
def lock_proposals(poll, request): """ Set proposals to voting. """ count = 0 for proposal in poll.get_proposal_objects(): try: proposal.set_workflow_state(request, 'voting') count += 1 except WorkflowError: # Skip those pass if count: fm = IFlashMessages(request, None) if fm: singular = request.localizer.translate(_(u"proposal")) plural = request.localizer.translate(_(u"proposals")) prop_form = request.localizer.pluralize(singular, plural, count) msg = _( u'poll_proposals_locked_notice', default=u"Setting ${count} ${prop_form} as 'locked for vote'. " u"They can no longer be edited or retracted by normal users. ", mapping={ 'count': count, 'prop_form': prop_form }) fm.add(msg)
def view_edit_manage_connected_profiles(self): schema = createSchema('ManageConnectedProfilesSchema').bind( context=self.context, request=self.request) form = Form(schema, buttons=(button_delete, button_cancel)) self.api.register_form_resources(form) #Handle submitted information if 'delete' in self.request.POST: controls = self.request.POST.items() try: appstruct = form.validate(controls) except ValidationFailure, e: self.response['form'] = e.render() return self.response domains = appstruct['auth_domains'] if domains: for domain in domains: del self.context.auth_domains[domain] msg = _(u"Removing information for: ${domains}", mapping={'domains': ", ".join(domains)}) self.api.flash_messages.add(msg) else: self.api.flash_messages.add(_(u"Nothing updated")) url = resource_url(self.context, self.request) return HTTPFound(location=url)
def inline_add_proposal_form(context, request, va, **kw): """ For agenda item contexts. """ api = kw['api'] jquery_form.need() #This isn't included in widgets for some reason form = inline_add_form(api, 'Proposal', {}) api.register_form_resources(form) if not api.context_has_permission(ADD_PROPOSAL, context): if context.get_workflow_state() == 'closed': msg = api.translate(_(u"no_propose_ai_closed", default = u"The agenda item is closed, you can't add a proposal here")) elif api.meeting.get_workflow_state() == 'closed': msg = api.translate(_(u"no_propose_meeting_closed", default = u"The meeting is closed, you can't add a proposal here")) else: msg = api.translate(_(u"no_propose_perm_notice", default = u"You don't have the required permission to add a proposal here")) return "<hr/>%s" % msg response = {} query = {'content_type': 'Proposal'} tag = request.GET.get('tag', None) if tag: query['tag'] = tag response['url'] = request.resource_url(context, '_inline_form', query = query) response['text'] = _(u'${username} propose', mapping={'username': api.userid}) return render('../templates/snippets/inline_dummy_proposal_button.pt', response, request = request)
def agenda_template_select(self): #FIXME: Should this be a migrate script? try: agenda_templates = self.api.root['agenda_templates'] except KeyError: # pragma: no coverage obj = createContent('AgendaTemplates', title=_(u"Agenda templates"), creators=['admin']) agenda_templates = self.api.root['agenda_templates'] = obj get = self.request.GET if 'apply' in get: template_name = get['apply'] if template_name in agenda_templates: template = agenda_templates[template_name] template.populate_meeting(self.context) msg = _(u"agenda_template_apply_template_success", default=u"Selected template applied to meeting.") self.api.flash_messages.add(msg) return HTTPFound( location=resource_url(self.context, self.request)) else: err_msg = _( u"agenda_template_apply_invalid_template", default=u"No template named ${template} could be found.", mapping={'template': template_name}) self.api.flash_messages.add(err_msg, type="error") self.response['agenda_templates'] = agenda_templates return self.response
def sort(self): self.response['title'] = _( u"order_agenda_template_view_title", default=u"Drag and drop agenda items to reorder") post = self.request.POST if 'cancel' in self.request.POST: url = resource_url(self.context, self.request) return HTTPFound(location=url) if 'save' in post: controls = self.request.POST.items() ais = self.context.get_field_value('agenda_items') order = 0 agenda_items = [] for (k, v) in controls: if k == 'agenda_items': ai = ais[int(v)] ai['order'] = order order += 1 agenda_items.append(ai) self.context.set_field_value('agenda_items', agenda_items) self.api.flash_messages.add(_(u'Order updated')) url = resource_url(self.context, self.request) return HTTPFound(location=url) return self.response
def inline_add_discussion_form(context, request, va, **kw): """ For agenda item contexts. """ api = kw['api'] jquery_form.need() #This isn't included in widgets for some reason form = inline_add_form(api, 'DiscussionPost', {}) api.register_form_resources(form) if not api.context_has_permission(ADD_DISCUSSION_POST, context): if api.meeting.get_workflow_state() == 'closed': msg = api.translate( _(u"no_discuss_meeting_closed", default= u"The meeting is closed, you can't add a discussion post here" )) else: msg = api.translate( _(u"no_discuss_perm_notice", default= u"You don't have the required permission to add a discussion post here" )) return "<hr/>%s" % msg response = {} response['user_image_tag'] = api.user_profile.get_image_tag( request=request) query = {'content_type': 'DiscussionPost'} tag = request.GET.get('tag', None) if tag: query['tag'] = tag response['url'] = request.resource_url(context, '_inline_form', query=query) response['text'] = _(u'Add') return render('../templates/snippets/inline_dummy_form.pt', response, request=request)
def vote_success(self, appstruct): #Just in case the form rendered before the poll closed if not self.can_vote: raise HTTPForbidden(_("You're not allowed to vote")) Vote = self.poll_plugin.get_vote_class() if not IVote.implementedBy(Vote): raise TypeError( "Poll plugins method get_vote_class returned something that didn't implement IVote." ) appstruct.pop('csrf_token', None) userid = self.request.authenticated_userid if userid in self.context: vote = self.context[userid] assert IVote.providedBy(vote), "%r doesn't provide IVote" % vote vote.set_vote_data(appstruct) success_msg = _("Your vote was changed.") else: vote = Vote(creators=[userid]) #We don't need to send events here, since object added will take care of that vote.set_vote_data(appstruct, notify=False) #To fire events after set_vote_data is done self.context[userid] = vote success_msg = _( "vote_success_msg", default="Your vote has been added. If you wish to change it, " "you may do so as long as the poll is open.") self.flash_messages.add(success_msg) return self._remove_modal_response()
def meeting_mail_name_node(): return colander.SchemaNode(colander.String(), title = _(u"Name of the contact person for this meeting"), default = _deferred_current_fullname, validator = colander.Regex(regex=NAME_PATTERN, msg=_(u"name_pattern_error", default = u"Must be at least 3 chars + only alphanumeric characters allowed")),)
def get_vote_schema(self, request=None, api=None): """ Get an instance of the schema that this poll uses. """ proposals = self.context.get_proposal_objects() #Choices should be something iterable with the contents [(UID for proposal, Title of proposal), <etc...>, ] choices = set() for prop in proposals: title = u"#%s - %s" % (prop.get_field_value('aid'), prop.title) choices.add((prop.uid, title)) poll_wf_state = self.context.get_workflow_state() if poll_wf_state == 'ongoing': proposal_title = _(u"Vote for one") else: proposal_title = _(u"You can't change your vote now.") class Schema(colander.Schema): proposal = colander.SchemaNode( colander.String(), validator=colander.OneOf([x[0] for x in choices]), widget=deform.widget.RadioChoiceWidget(values=choices), title=proposal_title, description=u'',) return Schema()
def meeting_default_description(node, kw): request = kw['request'] choices = dict(PROPOSAL_ORDER_CHOICES) title = choices.get(request.meeting.poll_proposals_default_order, _('Unknown')) title = request.localizer.translate(title) return _("Meeting default is currently: ${title}", mapping={'title': title})
def get_vote_schema(self, request=None, api=None): """ Get an instance of the schema that this poll uses. """ proposals = self.context.get_proposal_objects() #Choices should be something iterable with the contents [(UID for proposal, Title of proposal), <etc...>, ] choices = set() for prop in proposals: title = u"#%s - %s" % (prop.get_field_value('aid'), prop.title) choices.add((prop.uid, title)) poll_wf_state = self.context.get_workflow_state() if poll_wf_state == 'ongoing': proposal_title = _(u"Vote for one") else: proposal_title = _(u"You can't change your vote now.") class Schema(colander.Schema): proposal = colander.SchemaNode( colander.String(), validator=colander.OneOf([x[0] for x in choices]), widget=deform.widget.RadioChoiceWidget(values=choices), title=proposal_title, description=u'', ) return Schema()
def get_vote_schema(self): """ Get an instance of the schema that this poll uses. """ proposals = self.context.get_proposal_objects() # Choices should be something iterable with the contents [(UID for proposal, Title of proposal), <etc...>, ] choices = set() for prop in proposals: title = "#%s - %s" % (prop.get_field_value("aid"), prop.text) choices.add((prop.uid, title)) poll_wf_state = self.context.get_workflow_state() if poll_wf_state == "ongoing": proposal_title = _("Vote for one") else: proposal_title = _("You can't change your vote now.") class Schema(colander.Schema): widget = deform.widget.FormWidget( template="form_modal", readonly_template="readonly/form_modal") proposal = colander.SchemaNode( colander.String(), validator=colander.OneOf([x[0] for x in choices]), widget=deform.widget.RadioChoiceWidget(values=choices), title=proposal_title, description="", ) return Schema()
class ImmediateAP(AccessPolicy): """ Grant access for specific permissions immediately if a user requests it. No moderator approval requred. This is for very public meetings. """ name = 'public' title = _("Public access") description = _( "public_access_description", default= "Users will be granted the permissions you select without prior moderator approval. This is for public meetings." ) def schema(self): return colander.Schema( title=_("Would you like to participate?"), description=_( "Clicking request access will grant you access right away!")) def handle_success(self, view, appstruct): roles = self.context.get_field_value('immediate_access_grant_roles') if not roles: roles = _DEFAULT_ROLES self.context.add_groups(view.request.authenticated_userid, roles) view.flash_messages.add(_("Access granted - welcome!")) return HTTPFound(location=view.request.resource_url(self.context)) def config_schema(self): return ImmediateAPConfigSchema()
def password_node(): return colander.SchemaNode(colander.String(), validator=colander.All(password_validation, html_string_validator,), widget=deform.widget.CheckedPasswordWidget(size=20), title=_('Password'), description = _(u"password_creation_tip", default = u"Use at least 6 chars. A good rule is to use long passwords that " u"doesn't contain any personal information or things that someone else might guess."))
def recaptcha_node(): return colander.SchemaNode(colander.String(), #FIXME: write a good title and description here title=_(u"Verify you are human"), description = _(u"meeting_captcha_description", default=u"This is to prevent spambots from creating meetings"), missing=u"", widget=deferred_recaptcha_widget,)
def password_validation(node, value): """ check that password is - at least 6 chars and at most 100. """ if len(value) < 6: raise colander.Invalid(node, _(u"Too short. At least 6 chars required.")) if len(value) > 100: raise colander.Invalid(node, _(u"Less than 100 chars please."))
def public_description_node(): return colander.SchemaNode( colander.String(), title = _(u"Public presentation"), description = _(u"meeting_public_description_description", default=u"The public description is visible on the request access " u"page and to not yet logged in visitors."), missing = u"", widget=deform.widget.RichTextWidget(), validator=richtext_validator,)
def schema(self): #Just to make sure self.hashlist return colander.Schema( title=_("Would you like to participate?"), description=_( "Clicking request access will grant you access if your email address " "is allowed by the access policy." ) )
def manage_tickets(self): """ Handle and review tickets. """ if self.request.method == 'POST': data = self.request.POST.dict_of_lists() if 'email' not in data: self.flash_messages.add(_("Nothing selected - nothing to do!"), type = "danger") return HTTPFound(location = self.request.url) if 'remove' in data: for email in data['email']: del self.context.invite_tickets[email] self.flash_messages.add(_("Removed ${count} tickets", mapping = {'count': len(data['email'])})) return HTTPFound(location = self.request.url) if 'resend' in data: total = len(data['email']) if total > 1: #bulk send self.request.session['send_tickets.emails'] = data['email'] self.request.session['send_tickets.message'] = data['message'][0] self.request.session.changed() return HTTPFound(location = self.request.resource_url(self.context, 'send_tickets')) else: resent = 0 aborted = 0 for email in data['email']: ticket = self.context.invite_tickets[email] if not ticket.closed: send_invite_ticket(ticket, self.request, data['message'][0]) resent += 1 else: aborted += 1 if not aborted: msg = _(u"Resent ${count} successfully", mapping = {'count': resent}) else: msg = _(u"Resent ${count} of ${total}. ${aborted} were not sent since they're already claimed", mapping = {'count': resent, 'total': total, 'aborted': aborted}) self.flash_messages.add(msg) return HTTPFound(location = self.request.url) voteit_manage_tickets_js.need() #self.response['tabs'] = self.api.render_single_view_component(self.context, self.request, 'tabs', 'manage_tickets') closed = 0 results = [] never_invited = [] for ticket in self.context.invite_tickets.values(): results.append(ticket) if ticket.closed != None: closed += 1 if len(ticket.sent_dates) == 0: never_invited.append(ticket.email) response = {} response['invite_tickets'] = results response['closed_count'] = closed response['never_invited'] = never_invited response['roles_dict'] = dict(security.MEETING_ROLES) return response
def delete_success(self, appstruct): domains = appstruct['auth_domains'] if domains: for domain in domains: del self.context.auth_domains[domain] msg = _(u"Removing information for: ${domains}", mapping = {'domains': ", ".join(domains)}) self.api.flash_messages.add(msg) else: self.api.flash_messages.add(_(u"Nothing updated")) return HTTPFound(location = self.request.resource_url(self.context))
def __call__(self, node, value): if not NEW_USERID_PATTERN.match(value): msg = _('userid_char_error', default=u"UserID must be 3-30 chars, start with lowercase a-z and only contain lowercase a-z, numbers, minus and underscore.") raise colander.Invalid(node, msg) root = find_root(self.context) if value in root.users: msg = _('already_registered_error', default=u"UserID already registered. If it was registered by you, try to retrieve your password.") raise colander.Invalid(node, msg)
def no_html_validator(node, value): """ Checks that input doesn't contain html tags """ tag_re = re.compile(r'<.*?>', re.S) comment_re = re.compile(r'<!--|-->') if comment_re.match(value): raise colander.Invalid(node, _("HTML comments not allowed.")) if tag_re.match(value): raise colander.Invalid(node, _(u"HTML is not allowed."))
def description_node(): return colander.SchemaNode( colander.String(), title = _(u"Participants description"), description = _(u"meeting_description_description", default=u"This is only visible to participants, so don't put information on how to register here. " u"Displayed on the first page of the meeting. You can include things " u"like information about the meeting, how to contact the moderator and your logo."), missing = u"", widget=deform.widget.RichTextWidget(), validator=richtext_validator,)
def __call__(self, form, value): email = value['email'] token = self.context.invite_tickets.get(email) if not token: exc = colander.Invalid(form, 'Incorrect email') exc['token'] = _(u"Couldn't find any invitation for this email address.") raise exc if token.token != value['token']: exc = colander.Invalid(form, _(u"Email matches, but token doesn't")) exc['token'] = _(u"Check this field - token doesn't match") raise exc