Exemplo n.º 1
0
def vote_finish(request):
    """Renders the OpenID preference page after a successful voting process.

    The voter will be redirected to this page either after casting a vote or
    after any subsequent login to the system.

    The voter is allowed to select her OpenID preference once.

    If the voter chooses to keep the OpenID, he must provide valid
    contact information if it is not already known. Email, GSM, and
    address are required, but can be edited on the page.

    :param request: The currently active request.
    :type request: :py:class:`pyramid.request.Request`

    :rtype: dict
    """
    log = logging.getLogger('nuorisovaalit')
    request.add_response_callback(disable_caching)

    voter = authenticated_user(request)
    if voter is None:
        raise Forbidden()

    options = {
        'action_url': route_url('vote-finish', request),
        'csrf_token': request.session.get_csrf_token(),
        'accept_openid': voter.accept_openid,
        'message': u'Äänesi on tallennettu',
        'has_preference': voter.has_preference(),
        'pref_selected': False,
        'errors': [],
        'voter': voter,
        'gsm': request.POST.get('gsm', voter.gsm),
        'email': request.POST.get('email', voter.email),
        'street': request.POST.get('street', u''),
        'zipcode': request.POST.get('zipcode', u''),
        'city': request.POST.get('city', u''),
    }

    street = request.POST.get('street')
    zipcode = request.POST.get('zipcode')
    city = request.POST.get('city')

    # Parse the voter's address to the options if street, zipcode,
    # city were not provided.
    if (street, zipcode, city) == (None, None, None):
        match = RE_ADDRESS.match(voter.address or u'')
        if match:
            options['street'], options['zipcode'], options['city'] \
                               = (s.strip().strip(',').strip() for s in match.groups())
        elif voter.address is not None:
            # Put the whole address to the street field if it could
            # not be parsed.
            log.info('The address for user "{0}" in the database is partial.'.format(voter.openid))
            options['street'] = voter.address

    if request.session.get('vote_registered', 'no') == 'yes':
        log.info('User "{0}" returned after having voted already.'.format(voter.openid))
        options['message'] = u'Olet jo äänestänyt'

    if 'form.submitted' in request.POST:
        # CSRF protection.
        if request.session.get_csrf_token() != request.POST.get('csrf_token'):
            log.warn('CSRF attempt at: {0}.'.format(request.url))
            raise Forbidden()

        session = DBSession()

        # Voter has already selected his/her preference.
        if voter.has_preference():
            log.info('User "{0}" has already selected an OpenID preference, skipping.'.format(voter.openid))
            return exit_voting(request)

        # OpenID declined.
        if request.POST.get('use_open_identity') == 'no':
            voter.accept_openid = False
            session.add(voter)
            log.info('User "{0}" declined the OpenID account.'.format(voter.openid))
            return exit_voting(request)

        def normalize_gsm(gsm):
            """Remove whitespace and hyphens from the GSM number."""
            return u''.join(gsm.strip().replace('-', '').split())

        # GSM number is required.
        gsm = normalize_gsm(request.POST.get('gsm', u''))
        if RE_GSM.match(gsm) is None:
            options['errors'].append(
                u'GSM-numero on virheellinen, '
                u'esimerkki oikeasta muodosta "0501234567".')

        # Email is required.
        email = request.POST.get('email', u'').strip()
        if RE_EMAIL.match(email) is None:
            options['errors'].append(
                u'Sähköpostiosoite on virheellinen, '
                u'esimerkki oikeasta muodosta "*****@*****.**".')

        # Street is required.
        street = request.POST.get('street', u'').strip()
        if not street:
            options['errors'].append(u'Katuosoite puuttuu.')

        # Zipcode is required.
        zipcode = request.POST.get('zipcode', u'').strip()
        if len(zipcode) != 5 or not zipcode.isdigit():
            options['errors'].append(u'Postinumero on virheellinen, '
                                     u'esimerkki oikeasta muodosta "12345".')

        # City is required.
        city = request.POST.get('city', u'').strip()
        if not city:
            options['errors'].append(u'Postitoimipaikka puuttuu.')

        # OpenID preference must be selected.
        if request.POST.get('use_open_identity', None) is None:
            options['errors'].append(u'Valitse haluatko verkkovaikuttajaidentiteetin.')

        # Update voter info if no errors were found.
        if not options['errors']:
            voter.accept_openid = True
            voter.gsm = gsm
            voter.email = email
            voter.address = u'{0}, {1}, {2}'.format(street, zipcode, city)
            session.add(voter)
            log.info('User "{0}" accepted the OpenID account.'.format(voter.openid))
            return exit_voting(request)
        elif request.POST.get('use_open_identity', None) is not None:
            options['accept_openid'] = True
            options['pref_selected'] = True

        if options['errors']:  # pragma: no cover
            log.warn('User "{0}" failed validation for the OpenID preference.'.format(voter.openid))
            for error_msg in options['errors']:
                log.warn(u'Validation error: {0}'.format(error_msg))

    return options
Exemplo n.º 2
0
def select(request):
    """Renders the candidate selection list.

    The candidate list is generated based on the
    :py:class:`nuorisovaalit.models.District` the authenticated user is
    associated with. The user is only allowed to vote candidates in her own
    voting district.

    :param request: The currently active request.
    :type request: :py:class:`pyramid.request.Request`

    :rtype: dict
    """
    # Deco Grid positions for the candidate columns.
    positions = ['0', '1:3', '2:3']
    columns = len(positions)
    session = DBSession()
    log = logging.getLogger('nuorisovaalit')
    # Disable caching
    request.add_response_callback(disable_caching)

    # Require authentication.
    voter = authenticated_user(request)
    if voter is None:
        log.warn('Unauthenticated attempt to select candidates.')
        raise Forbidden()

    # User should vote only once.
    if voter.has_voted():
        log.warn('User "{0}" attempted to select candidates after voting.'.format(voter.openid))
        return HTTPFound(location=route_url('vote-finish', request))

    # Query the candidates in the voter's election district.
    query = session.query(Party, Candidate)\
                .filter(Candidate.party_id == Party.id)\
                .filter(Candidate.district_id == voter.school.district.id)\
                .order_by(Party.name, Candidate.number)

    candidates_by_party = {}
    for party, candidate in query.all():
        candidates_by_party.setdefault(party.name, []).append(candidate)

    parties = []
    for party_name, candidates in sorted(candidates_by_party.items()):
        parties.append({
            'title': party_name,
            'candidates': split_candidates([
                dict(name=c.fullname(), number=c.number, url=route_url('vote', request, number=c.number))
                for c in candidates], columns),
            'positions': cycle(positions),
            })

    coalitions = []
    for coalition in session.query(Coalition).filter_by(district_id=voter.school.district.id).all():
        coalitions.append(u', '.join(sorted(party.name for party in coalition.parties)))

    options = {
        'parties': parties,
        'columns': columns,
        'coalitions': coalitions,
        'district': voter.school.district.name,
        'empty_vote_url': route_url('vote', request, number=Candidate.EMPTY_CANDIDATE),
        }

    return options
Exemplo n.º 3
0
def vote(request):
    """Renders the voting form for the selected candidate and processes the
    vote.

    A valid vote must meet all of the following criteria:

        * The voter must be authenticated.

        * The voter must not have voted previously.

        * The candidate must be the one chosen in the previous step (See
          :py:func:`select`).

        * The candidate must belong to the same voting district as the voter.

        * The CSRF token included in the form must be valid.

    :param request: The currently active request.
    :type request: :py:class:`pyramid.request.Request`

    :rtype: dict
    """
    error = False
    session = DBSession()
    voter = authenticated_user(request)
    log = logging.getLogger('nuorisovaalit')
    request.add_response_callback(disable_caching)

    # The user must be authenticated at this time
    if voter is None:
        log.warn('Unauthenticated attempt to vote.')
        raise Forbidden()

    # The user may vote only once
    if voter.has_voted():
        log.warn('User "{0}" attempted to vote a second time.'.format(voter.openid))
        return HTTPFound(location=route_url('vote-finish', request))

    # The selected candidate must either be the special empty candidate or a
    # valid candidate in the voter's voting district.
    candidate = session.query(Candidate)\
            .filter(Candidate.number == int(request.matchdict['number']))\
            .filter(or_(Candidate.district_id == voter.school.district.id,
                        Candidate.number == Candidate.EMPTY_CANDIDATE))\
            .first()

    if candidate is None:
        log.warn('User "{0}" attempted to vote for a non-existing candidate "{1}".'.format(
            voter.openid, request.matchdict['number']))
        raise NotFound

    # Handle voting
    if 'vote' in request.POST:

        if request.session.get_csrf_token() != request.POST.get('csrf_token'):
            log.warn('CSRF attempt at: {0}.'.format(request.url))
            error = True
        elif request.POST['vote'].strip() == request.matchdict['number']:

            session.add(Vote(candidate, voter.school, Vote.ELECTRONIC, 1))
            session.add(VotingLog(voter))

            log.info('Stored vote cast by "{0}".'.format(voter.openid))
            return HTTPFound(location=route_url('vote-finish', request))
        else:
            error = True

    options = {
        'action_url': request.path_url,
        'select_url': route_url('select', request),
        'candidate': {
            'number': candidate.number,
            'name': candidate.fullname(),
            },
        'profile': {
            'fullname': voter.fullname(),
            'district': voter.school.district.name,
        },
        'error': error,
        'csrf_token': request.session.get_csrf_token(),
    }

    return options