Esempio n. 1
0
class AddMultiVotesSchema(BaseMultiVotesSchema):
    confirm = colander.SchemaNode(
        colander.Bool(),
        title=_("I understand the consequences of adding this"),
        validator=colander.Function(_only_true_validator,
                                    _("Confirmation not clicked")),
    )
Esempio n. 2
0
def includeme(config):
    config.add_view_action(
        control_panel_category,
        "control_panel",
        "multivotes",
        panel_group="control_panel_multivotes",
        title=_("Multiple votes"),
        description=
        _("Allow users to have multiple votes. This will change how you're able to invite users too."
          ),
        permission=security.MODERATE_MEETING,
        check_active=_is_active,
    )
    config.add_view_action(
        only_when_inactive(control_panel_link),
        "control_panel_multivotes",
        "activate",
        title=_("Activate..."),
        view_name="_activate_multivotes",
    )
    config.add_view_action(
        create_and_assign_link,
        "control_panel_multivotes",
        "create_assignments",
        title=_("Create and assign"),
        view_name="create_assignments",
    )
    config.add_view_action(multivotes_nav, "nav_meeting", "multivotes")
    config.add_view_action(multivotes_nav, "control_panel_multivotes",
                           "multivotes_page")
Esempio n. 3
0
def validate_rows(node, value):
    i = 1
    schema = VoteAssignmenRow()
    for row in value:
        if len(row) == 2:
            # To avoid trim
            row.append("")
        if len(row) == 3:
            try:
                schema.deserialize(row)
            except colander.Invalid as exc:
                request = get_current_request()
                for (k, v) in exc.asdict(
                        translate=request.localizer.translate).items():
                    msg = _(
                        "colander_csv_error",
                        default="Row ${num} - item ${item} error: ${err_msg}",
                        mapping={
                            "num": i,
                            "item": k,
                            "err_msg": v
                        },
                    )
                    raise colander.Invalid(node, msg)
        elif len(row) == 1 and not row[0]:
            pass
        else:
            raise colander.Invalid(
                node, _("Row ${num} must have 3 elements", mapping={"num": i}))
        i += 1
Esempio n. 4
0
class LockAssignmentWorkflow(Workflow):
    name = "lock_assignment"
    title = _("Lock assignment workflow")
    # description = _("")
    states = {"open": _("Open"), "locked": _("Locked")}
    transitions = {}
    initial_state = "open"

    @classmethod
    def init_acl(cls, registry):
        aclreg = registry.acl
        # Open
        open_name = "%s:open" % cls.name
        open_acl = aclreg.new_acl(open_name, title=_("Open"))
        open_acl.add(security.ROLE_ADMIN, security.ALL_PERMISSIONS)
        open_acl.add(
            security.ROLE_MODERATOR,
            [security.MODERATE_MEETING, security.EDIT, ADD_VOTE_ASSIGNMENT])
        open_acl.add(security.ROLE_VIEWER, [security.VIEW])
        # Locked
        locked_name = "%s:locked" % cls.name
        locked_acl = aclreg.new_acl(locked_name, title=_("Locked"))
        locked_acl.add(security.ROLE_ADMIN,
                       [security.MODERATE_MEETING, security.VIEW])
        locked_acl.add(security.ROLE_MODERATOR, [security.MODERATE_MEETING])
        locked_acl.add(security.ROLE_VIEWER, [security.VIEW])
Esempio n. 5
0
 def __call__(self):
     block_during_ongoing_poll(self.context)
     if not self.request.has_permission(ADD_VOTE_ASSIGNMENT, self.context):
         if self.context.wf_state == 'locked':
             msg = _("Not allowed when assignment is locked. Open it to use this")
             self.flash_messages.add(msg, type="danger")
             raise HTTPFound(location=self.request.resource_url(self.context))
         else:
             raise HTTPForbidden(_("Forbidden"))
     return super(CreateAndAssignForm, self).__call__()
Esempio n. 6
0
class BaseMultiVotesSchema(colander.Schema):
    title = colander.SchemaNode(
        colander.String(),
        validator=colander.Length(max=20),
        title=_("Short title, used in navigation too"),
        description=_(
            "multivotes_schema_title_description",
            default=
            "Max 20 chars. Something like 'Organisations' or 'Vote power'. "
            "Basically the reason that someone has several votes.",
        ),
    )
Esempio n. 7
0
 def init_acl(cls, registry):
     aclreg = registry.acl
     # Open
     open_name = "%s:open" % cls.name
     open_acl = aclreg.new_acl(open_name, title=_("Open"))
     open_acl.add(security.ROLE_ADMIN, security.ALL_PERMISSIONS)
     open_acl.add(
         security.ROLE_MODERATOR,
         [security.MODERATE_MEETING, security.EDIT, ADD_VOTE_ASSIGNMENT])
     open_acl.add(security.ROLE_VIEWER, [security.VIEW])
     # Locked
     locked_name = "%s:locked" % cls.name
     locked_acl = aclreg.new_acl(locked_name, title=_("Locked"))
     locked_acl.add(security.ROLE_ADMIN,
                    [security.MODERATE_MEETING, security.VIEW])
     locked_acl.add(security.ROLE_MODERATOR, [security.MODERATE_MEETING])
     locked_acl.add(security.ROLE_VIEWER, [security.VIEW])
Esempio n. 8
0
class VoteAssignmentCreateCSV(colander.Schema):
    csv_items = colander.SchemaNode(
        colander.String(),
        title=_(
            "paste_spreadsheet_text_title",
            default=
            "Paste rows from Excel, Google docs, Libre-office or similar.",
        ),
        description=_(
            "paste_spreadsheet_text_description",
            default=
            "Columns must be: Title of organisation, Number of votes, user email (or blank).",
        ),
        widget=deform.widget.TextAreaWidget(rows=10),
        preparer=splitrows,
        validator=validate_rows,
    )
Esempio n. 9
0
def block_starting_polls_with_multivotes_open(context, event):
    if event.new_state == "ongoing":
        meeting = find_interface(context, IMeeting)
        if MEETING_NAMESPACE in meeting:
            mv = meeting[MEETING_NAMESPACE]
            if mv.wf_state == "open":
                raise HTTPForbidden(
                    _("You may not start polls unless you've locked the assignment of votes. See multivotes."
                      ))
Esempio n. 10
0
def block_during_ongoing_poll(context):
    if check_ongoing_poll(context):
        raise HTTPForbidden(
            _(
                "access_during_ongoing_not_allowed",
                default="During ongoing polls, this action isn't allowed. "
                "Try again when polls have closed.",
            )
        )
Esempio n. 11
0
class CreateAndAssignForm(BaseForm):
    title = _("Create and assign")
    type_name = "VoteAssignment"
    schema_name = "create_csv"

    def __call__(self):
        block_during_ongoing_poll(self.context)
        if not self.request.has_permission(ADD_VOTE_ASSIGNMENT, self.context):
            if self.context.wf_state == 'locked':
                msg = _("Not allowed when assignment is locked. Open it to use this")
                self.flash_messages.add(msg, type="danger")
                raise HTTPFound(location=self.request.resource_url(self.context))
            else:
                raise HTTPForbidden(_("Forbidden"))
        return super(CreateAndAssignForm, self).__call__()

    def save_success(self, appstruct):
        factory = self.request.content_factories["VoteAssignment"]
        get_user_by_email = self.request.root["users"].get_user_by_email
        va_schema = VoteAssignmentSchema().bind(
            context=self.context, request=self.request
        )
        obj_count = 0
        assign_count = 0
        not_found_count = 0
        for row in appstruct["csv_items"]:
            userid = ""
            email = row[2]
            if email:
                # Only assign users that can be found via a validated email address
                # Blank assignment is ok
                user = get_user_by_email(email, only_validated=True)
                if user is None:
                    not_found_count += 1
                else:  # email not found
                    userid = user.userid
                    assign_count += 1
            payload = {"title": row[0], "votes": row[1], "userid_assigned": userid}
            kw = va_schema.deserialize(payload)
            obj = factory(**kw)
            self.context[obj.uid] = obj
            obj_count += 1
        msg = _(
            "created_msg_summary",
            default="Created ${obj_count} objects. ${assign_count} users were assigned via email. "
            "${not_found_count} email addresses didn't match a registered user.",
            mapping={
                "obj_count": obj_count,
                "assign_count": assign_count,
                "not_found_count": not_found_count,
            },
        )
        self.flash_messages.add(msg, type="info", auto_destruct=False)
        return HTTPFound(location=self.request.resource_url(self.context))
Esempio n. 12
0
class VoteAssignment(Base):
    """ Votes are assigned with this item when using multi-vote system. A single user is assigned votes. """

    type_name = "VoteAssignment"
    type_title = _("Vote Assignment")
    type_description = ""
    add_permission = ADD_VOTE_ASSIGNMENT
    naming_attr = "uid"
    title = ""
    votes = 1
    userid_assigned = None
Esempio n. 13
0
class VoteAssignmentSchema(colander.Schema):
    title = colander.SchemaNode(
        colander.String(),
        title=
        _("Name of voting asignment - like the organisation that this represents"
          ),
    )
    votes = colander.SchemaNode(
        colander.Int(),
        title=_("Number of votes"),
        description=_("1-500 is a valid number"),
        default=1,
        validator=colander.Range(max=500, min=1),
    )
    userid_assigned = colander.SchemaNode(
        colander.String(),
        title=_("The user assigned"),
        widget=MeetingUserReferenceWidget(multiple=False),
        validator=existing_userids,
        missing="",
    )
Esempio n. 14
0
 def save_success(self, appstruct):
     factory = self.request.content_factories["VoteAssignment"]
     get_user_by_email = self.request.root["users"].get_user_by_email
     va_schema = VoteAssignmentSchema().bind(
         context=self.context, request=self.request
     )
     obj_count = 0
     assign_count = 0
     not_found_count = 0
     for row in appstruct["csv_items"]:
         userid = ""
         email = row[2]
         if email:
             # Only assign users that can be found via a validated email address
             # Blank assignment is ok
             user = get_user_by_email(email, only_validated=True)
             if user is None:
                 not_found_count += 1
             else:  # email not found
                 userid = user.userid
                 assign_count += 1
         payload = {"title": row[0], "votes": row[1], "userid_assigned": userid}
         kw = va_schema.deserialize(payload)
         obj = factory(**kw)
         self.context[obj.uid] = obj
         obj_count += 1
     msg = _(
         "created_msg_summary",
         default="Created ${obj_count} objects. ${assign_count} users were assigned via email. "
         "${not_found_count} email addresses didn't match a registered user.",
         mapping={
             "obj_count": obj_count,
             "assign_count": assign_count,
             "not_found_count": not_found_count,
         },
     )
     self.flash_messages.add(msg, type="info", auto_destruct=False)
     return HTTPFound(location=self.request.resource_url(self.context))
Esempio n. 15
0
class MultiVotes(Content, ContextACLMixin):
    """ A container for vote assignment when the multi vote system is activated. """

    type_name = "MultiVotes"
    type_title = _("MultiVotes")
    type_description = ""
    add_permission = MODERATE_MEETING
    css_icon = "glyphicon glyphicon-folder-open"
    nav_visible = True
    listing_visible = True
    search_visible = False
    title = _("MultiVotes")
    total_votes = 0
    total_voters = 0
    assigned_votes = 0

    def get_sorted_values(self):
        """ Return all contained Group object sorted on title. """
        return sorted(self.values(), key=lambda x: x.title.lower())

    def get_assignments(self, userid):
        """ Get the users assignments. """
        for v in self.values():
            if v.userid_assigned == userid:
                yield v

    def get_vote_power(self, userid):
        """ How many votes should this user have based on the number of assignments they have.
        """
        votes = 0
        for v in self.get_assignments(userid):
            votes += v.votes
        return votes

    def update_totals(self):
        total_votes = 0
        assigned_votes = 0
        voters = set()
        for v in self.values():
            total_votes += v.votes
            if v.userid_assigned:
                assigned_votes += v.votes
                voters.add(v.userid_assigned)
        self.total_votes = total_votes
        self.total_voters = len(voters)
        self.assigned_votes = assigned_votes

    def get_voters(self):
        results = set()
        for v in self.values():
            if v.userid_assigned:
                results.add(v.userid_assigned)
        return results

    def recheck_voters(self):
        """ Perform a check against the current voters and who's actually in a vote assignment. """
        meeting = self.__parent__
        lr = meeting.local_roles
        current_role_voters = frozenset(lr.get_any_local_with(ROLE_VOTER))
        current_mv_voters = self.get_voters()
        # Make sure the voting role matches the users that should have a vote.
        # Invitations and similar doesn't matter in a multivote meeting
        remove_userids = current_role_voters - current_mv_voters
        add_userids = current_mv_voters - current_role_voters
        for userid in remove_userids:
            lr.remove(userid, ROLE_VOTER, event=False)
        for userid in add_userids:
            lr.add(userid, ROLE_VOTER, event=False)
        if remove_userids or add_userids:
            # Send one event afterwards to avoid clutter
            lr.send_event()
Esempio n. 16
0
        open_acl.add(
            security.ROLE_MODERATOR,
            [security.MODERATE_MEETING, security.EDIT, ADD_VOTE_ASSIGNMENT])
        open_acl.add(security.ROLE_VIEWER, [security.VIEW])
        # Locked
        locked_name = "%s:locked" % cls.name
        locked_acl = aclreg.new_acl(locked_name, title=_("Locked"))
        locked_acl.add(security.ROLE_ADMIN,
                       [security.MODERATE_MEETING, security.VIEW])
        locked_acl.add(security.ROLE_MODERATOR, [security.MODERATE_MEETING])
        locked_acl.add(security.ROLE_VIEWER, [security.VIEW])


LockAssignmentWorkflow.add_transitions(
    from_states="open",
    to_states="locked",
    title=_("Lock vote changes"),
    permission=security.MODERATE_MEETING,
)

LockAssignmentWorkflow.add_transitions(
    from_states="locked",
    to_states="open",
    title=_("Enable vote updates"),
    permission=security.MODERATE_MEETING,
)


def includeme(config):
    config.add_workflow(LockAssignmentWorkflow)
Esempio n. 17
0
 def __call__(self):
     self.context.userid_assigned = None
     self.flash_messages.add(_("Cleared assigned user from ${title}", mapping={'title': self.context.title}))
     return redirect_parent(self.context, self.request)