Beispiel #1
0
def tidbits_by_book(request, book):
    book = book.replace('-',' ')
    startverse = Verse.objects.filter(book__iexact=book).order_by('id')[:1]
    if startverse.count() == 0:
        return HttpResponse(book + " is not valid!")
    endverse = Verse.objects.filter(book__iexact=book).order_by('-id')[:1]
    cfs = CrossRef.objects.filter(startverse__gte=startverse, endverse__lte=endverse)

    from django.db.models.query import EmptyQuerySet
    tidbits = EmptyQuerySet()
    for cf in cfs:
        tidbits |= cf.tidbit_set.all()
    tidbits = tidbits.distinct()
    c = {
        'filter_criteria': book,
        'filter_count': tidbits.count(),
        'tidbits': paginate_tidbits(request, tidbits),
        'total_count': Tidbit.objects.all().count()
    }
    return render_to_response("tidbits_home.html", c, context_instance=RequestContext(request))
Beispiel #2
0
def tidbits_by_book(request, book):
    book = book.replace('-', ' ')
    startverse = Verse.objects.filter(book__iexact=book).order_by('id')[:1]
    if startverse.count() == 0:
        return HttpResponse(book + " is not valid!")
    endverse = Verse.objects.filter(book__iexact=book).order_by('-id')[:1]
    cfs = CrossRef.objects.filter(startverse__gte=startverse,
                                  endverse__lte=endverse)

    from django.db.models.query import EmptyQuerySet
    tidbits = EmptyQuerySet()
    for cf in cfs:
        tidbits |= cf.tidbit_set.all()
    tidbits = tidbits.distinct()
    c = {
        'filter_criteria': book,
        'filter_count': tidbits.count(),
        'tidbits': paginate_tidbits(request, tidbits),
        'total_count': Tidbit.objects.all().count()
    }
    return render_to_response("tidbits_home.html",
                              c,
                              context_instance=RequestContext(request))
Beispiel #3
0
def voters_email(request, election, poll=None, voter_uuid=None):
    user = request.admin

    TEMPLATES = [
        ('vote', _('Time to Vote')),
        ('info', _('Additional Info')),
    ]

    default_template = 'vote'

    if not election.any_poll_feature_can_send_voter_mail:
        raise PermissionDenied('34')

    if not election.any_poll_feature_can_send_voter_booth_invitation:
        TEMPLATES.pop(0)
        default_template = 'info'

    if election.voting_extended_until and not election.voting_ended_at:
        TEMPLATES.append(('extension', _('Voting end date extended')))

    template = request.REQUEST.get('template', default_template)

    if not template in [t[0] for t in TEMPLATES]:
        raise Exception("bad template")

    polls = [poll]
    if not poll:
        polls = election.polls_by_link_id

    voter = None
    if voter_uuid:
        try:
            if poll:
                voter = get_object_or_404(Voter, uuid=voter_uuid, poll=poll)
            else:
                voter = get_object_or_404(Voter,
                                          uuid=voter_uuid,
                                          election=election)
        except Voter.DoesNotExist:
            raise PermissionDenied('35')
        if not voter:
            url = election_reverse(election, 'index')
            return HttpResponseRedirect(url)

    election_url = election.get_absolute_url()

    default_subject = render_to_string('email/%s_subject.txt' % template,
                                       {'custom_subject': "<SUBJECT>"})

    default_body = render_to_string(
        'email/%s_body.txt' % template, {
            'election': election,
            'election_url': election_url,
            'custom_subject': default_subject,
            'custom_message': '<BODY>',
            'voter': {
                'vote_hash': '<SMART_TRACKER>',
                'name': '<VOTER_NAME>',
                'voter_name': '<VOTER_NAME>',
                'voter_surname': '<VOTER_SURNAME>',
                'voter_login_id': '<VOTER_LOGIN_ID>',
                'voter_password': '******',
                'audit_passwords': '1',
                'get_audit_passwords': ['pass1', 'pass2', '...'],
                'get_quick_login_url': '<VOTER_LOGIN_URL>',
                'poll': poll,
                'election': election
            }
        })

    q_param = request.GET.get('q', None)

    filtered_voters = election.voters.filter()
    if poll:
        filtered_voters = poll.voters.filter()

    if not q_param:
        filtered_voters = EmptyQuerySet()
    else:
        voters_filters = get_filters(q_param, VOTER_TABLE_HEADERS,
                                     VOTER_SEARCH_FIELDS, VOTER_BOOL_KEYS_MAP,
                                     VOTER_EXTRA_HEADERS)
        filtered_voters = filtered_voters.filter(voters_filters)

        if not filtered_voters.count():
            message = _("No voters were found.")
            messages.error(request, message)
            url = election_reverse(election, 'polls_list')
            return HttpResponseRedirect(url)

    if request.method == "GET":
        email_form = EmailVotersForm()
        email_form.fields['subject'].initial = dict(TEMPLATES)[template]
        if voter:
            email_form.fields['send_to'].widget = \
                email_form.fields['send_to'].hidden_widget()
    else:
        email_form = EmailVotersForm(request.POST)
        if email_form.is_valid():
            # the client knows to submit only once with a specific voter_id
            voter_constraints_include = None
            voter_constraints_exclude = None
            update_booth_invitation_date = False
            if template == 'vote':
                update_booth_invitation_date = True

            if voter:
                voter_constraints_include = {'uuid': voter.uuid}

            # exclude those who have not voted
            if email_form.cleaned_data['send_to'] == 'voted':
                voter_constraints_exclude = {'vote_hash': None}

            # include only those who have not voted
            if email_form.cleaned_data['send_to'] == 'not-voted':
                voter_constraints_include = {'vote_hash': None}

            for _poll in polls:
                if not _poll.feature_can_send_voter_mail:
                    continue

                if template == 'vote' and not \
                        _poll.feature_can_send_voter_booth_invitation:
                    continue

                subject_template = 'email/%s_subject.txt' % template
                body_template = 'email/%s_body.txt' % template
                extra_vars = {
                    'custom_subject': email_form.cleaned_data['subject'],
                    'custom_message': email_form.cleaned_data['body'],
                    'election_url': election_url,
                }
                task_kwargs = {
                    'subject_template': subject_template,
                    'body_template': body_template,
                    'extra_vars': extra_vars,
                    'voter_constraints_include': voter_constraints_include,
                    'voter_constraints_exclude': voter_constraints_exclude,
                    'update_date': True,
                    'update_booth_invitation_date':
                    update_booth_invitation_date,
                    'q_param': q_param,
                }
                log_obj = election
                if poll:
                    log_obj = poll
                if voter:
                    log_obj.logger.info(
                        "Notifying single voter %s, [template: %s, filter: %s]",
                        voter.voter_login_id, template, q_param)
                else:
                    log_obj.logger.info(
                        "Notifying voters, [template: %s, filter: %r]",
                        template, q_param)
                tasks.voters_email.delay(_poll.pk, **task_kwargs)

            filters = get_voters_filters_with_constraints(
                q_param, voter_constraints_include, voter_constraints_exclude)
            send_to = filtered_voters.filter(filters)
            if q_param and not send_to.filter(filters).count():
                msg = "No voters matched your filters. No emails were sent."
                messages.error(request, _(msg))
            else:
                messages.info(request, _("Email sending started"))

            url = election_reverse(election, 'polls_list')
            if poll:
                url = poll_reverse(poll, 'voters')
            if q_param:
                url += '?q=%s' % urllib.quote_plus(q_param)
            return HttpResponseRedirect(url)

    context = {
        'email_form': email_form,
        'election': election,
        'poll': poll,
        'voter_o': voter,
        'default_subject': default_subject,
        'default_body': default_body,
        'template': template,
        'filtered_voters': filtered_voters,
        'templates': TEMPLATES
    }
    set_menu('voters', context)
    if not poll:
        set_menu('polls', context)
    return render_template(request, "voters_email", context)
Beispiel #4
0
def voters_email(request, election, poll=None, voter_uuid=None):
    user = request.admin

    TEMPLATES = [
        ('vote', _('Time to Vote')),
        ('info', _('Additional Info')),
    ]


    default_template = 'vote'

    if not election.any_poll_feature_can_send_voter_mail:
        raise PermissionDenied('34')

    if not election.any_poll_feature_can_send_voter_booth_invitation:
        TEMPLATES.pop(0)
        default_template = 'info'

    if election.voting_extended_until and not election.voting_ended_at:
        TEMPLATES.append(('extension', _('Voting end date extended')))

    template = request.REQUEST.get('template', default_template)

    if not template in [t[0] for t in TEMPLATES]:
        raise Exception("bad template")

    polls = [poll]
    if not poll:
        polls = election.polls_by_link_id

    voter = None
    if voter_uuid:
        try:
            if poll:
                voter = get_object_or_404(Voter, uuid=voter_uuid, poll=poll)
            else:
                voter = get_object_or_404(Voter, uuid=voter_uuid,
                                          election=election)
        except Voter.DoesNotExist:
            raise PermissionDenied('35')
        if not voter:
            url = election_reverse(election, 'index')
            return HttpResponseRedirect(url)

    election_url = election.get_absolute_url()

    default_subject = render_to_string(
        'email/%s_subject.txt' % template, {
            'custom_subject': "&lt;SUBJECT&gt;"
        })

    default_body = render_to_string(
        'email/%s_body.txt' % template, {
            'election' : election,
            'election_url' : election_url,
            'custom_subject' : default_subject,
            'custom_message': '&lt;BODY&gt;',
            'voter': {
                'vote_hash' : '<SMART_TRACKER>',
                'name': '<VOTER_NAME>',
                'voter_name': '<VOTER_NAME>',
                'voter_surname': '<VOTER_SURNAME>',
                'voter_login_id': '<VOTER_LOGIN_ID>',
                'voter_password': '******',
                'audit_passwords': '1',
                'get_audit_passwords': ['pass1', 'pass2', '...'],
                'get_quick_login_url': '<VOTER_LOGIN_URL>',
                'poll': poll,
                'election' : election}
            })

    q_param = request.GET.get('q', None)

    filtered_voters = election.voters.filter()
    if poll:
        filtered_voters = poll.voters.filter()

    if not q_param:
        filtered_voters = EmptyQuerySet()
    else:
        voters_filters = get_filters(q_param, VOTER_TABLE_HEADERS,
                                     VOTER_SEARCH_FIELDS,
                                     VOTER_BOOL_KEYS_MAP, 
                                     VOTER_EXTRA_HEADERS)
        filtered_voters = filtered_voters.filter(voters_filters)

        if not filtered_voters.count():
            message = _("No voters were found.")
            messages.error(request, message)
            url = election_reverse(election, 'polls_list')
            return HttpResponseRedirect(url)

    if request.method == "GET":
        email_form = EmailVotersForm()
        email_form.fields['subject'].initial = dict(TEMPLATES)[template]
        if voter:
            email_form.fields['send_to'].widget = \
                email_form.fields['send_to'].hidden_widget()
    else:
        email_form = EmailVotersForm(request.POST)
        if email_form.is_valid():
            # the client knows to submit only once with a specific voter_id
            voter_constraints_include = None
            voter_constraints_exclude = None
            update_booth_invitation_date = False
            if template == 'vote':
                update_booth_invitation_date = True

            if voter:
                voter_constraints_include = {'uuid': voter.uuid}

            # exclude those who have not voted
            if email_form.cleaned_data['send_to'] == 'voted':
                voter_constraints_exclude = {'vote_hash' : None}

            # include only those who have not voted
            if email_form.cleaned_data['send_to'] == 'not-voted':
                voter_constraints_include = {'vote_hash': None}

            for _poll in polls:
                if not _poll.feature_can_send_voter_mail:
                    continue

                if template == 'vote' and not \
                        _poll.feature_can_send_voter_booth_invitation:
                    continue

                subject_template = 'email/%s_subject.txt' % template
                body_template = 'email/%s_body.txt' % template
                extra_vars = {
                    'custom_subject' : email_form.cleaned_data['subject'],
                    'custom_message' : email_form.cleaned_data['body'],
                    'election_url' : election_url,
                }
                task_kwargs = {
                    'subject_template': subject_template,
                    'body_template': body_template,
                    'extra_vars': extra_vars,
                    'voter_constraints_include': voter_constraints_include,
                    'voter_constraints_exclude': voter_constraints_exclude,
                    'update_date': True,
                    'update_booth_invitation_date': update_booth_invitation_date,
                    'q_param': q_param,
                }
                log_obj = election
                if poll:
                    log_obj = poll
                if voter:
                    log_obj.logger.info("Notifying single voter %s, [template: %s, filter: %s]",
                                     voter.voter_login_id, template, q_param)
                else:
                    log_obj.logger.info("Notifying voters, [template: %s, filter: %r]", template, q_param)
                tasks.voters_email.delay(_poll.pk, **task_kwargs)

            filters = get_voters_filters_with_constraints(q_param,
                        voter_constraints_include, voter_constraints_exclude)
            send_to = filtered_voters.filter(filters)
            if q_param and not send_to.filter(filters).count():
                msg = "No voters matched your filters. No emails were sent."
                messages.error(request, _(msg))
            else:
                messages.info(request, _("Email sending started"))

            url = election_reverse(election, 'polls_list')
            if poll:
                url = poll_reverse(poll, 'voters')
            if q_param:
                url += '?q=%s' % urllib.quote_plus(q_param)
            return HttpResponseRedirect(url)

    context = {
        'email_form': email_form,
        'election': election,
        'poll': poll,
        'voter_o': voter,
        'default_subject': default_subject,
        'default_body': default_body,
        'template': template,
        'filtered_voters': filtered_voters,
        'templates': TEMPLATES
    }
    set_menu('voters', context)
    if not poll:
        set_menu('polls', context)
    return render_template(request, "voters_email", context)
Beispiel #5
0
def tidbits_by_passage(request, user=None):
    if request.method == 'GET':
        passage_ref = request.GET['passage_ref']
        try:
            startverse, endverse = CrossRef.objects.parse_reference(
                passage_ref)
        except Exception as e:
            # TODO: make this neater
            return HttpResponse(str(e.args[0]))

        # regex = re.compile("(?P<book>([12]?[A-Za-z ]+[A-Za-z]))( ?(?P<start_chapter>[0-9]+)((:(?P<start_verse>[0-9]+))? ?(- ?(?P<end_chp_or_verse>[0-9]+)(:(?P<end_verse>[0-9]+))?)?)?)?$")
        # matches = regex.search(passage_ref)
        # if matches == None:
        #     # TODO: make this neater
        #     return HttpResponse("Invalid passage reference: " + passage_ref)
        # groups = matches.groupdict()
        # book = groups['book']
        # startchp = groups['start_chapter']
        # startvs = groups['start_verse']
        # endchporvs = groups['end_chp_or_verse']
        # endvs = groups['end_verse']
        # cfs = None

        # if not startchp:
        #     startverse = Verse.objects.filter(book__iexact=book).order_by('id')[:1]
        #     if startverse.count() == 0:
        #         return HttpResponse(book + " is not valid!")
        #     endverse = Verse.objects.filter(book__iexact=book).order_by('-id')[:1]
        #     cfs = CrossRef.objects.filter(startverse__gte=startverse, endverse__lte=endverse)
        # elif not startvs:
        #     startverse = Verse.objects.filter(
        #                     book__iexact=book,
        #                     chapter_ref=int(startchp),
        #                     verse_ref=1
        #                 ).order_by('id')[:1]
        #     if startverse.count() == 0:
        #         return HttpResponse(book + " " + int(startchp) + " is not valid!")
        #     endverse = Verse.objects.filter(
        #                     book__iexact=book,
        #                     chapter_ref=int(startchp)
        #                 ).order_by('-id')[:1]
        #     cfs = CrossRef.objects.filter(startverse__gte=startverse, endverse__lte=endverse)
        # elif not endchporvs:
        #     endchp = startchp
        #     endvs = startvs
        # elif not endvs:
        #     endvs = endchporvs
        #     endchp = startchp
        # else:
        #     endchp = endchporvs

        # startverse = Verse.objects.get(book__istartswith=book,
        #                                 chapter_ref=int(startchp),
        #                                 verse_ref=int(startvs))
        # endverse = Verse.objects.get(book__istartswith=book,
        #                                 chapter_ref=int(endchp),
        #                                 verse_ref=int(endvs))
        # if startverse.id > endverse.id:
        #     # TODO: handle this
        #     return HttpResponse("Start verse " + startverse + " must come before end verse " + endverse)

        # get tidbits where a cross ref intersects with a given range (more general)
        cfs = CrossRef.objects.filter(startverse__gte=startverse,
                                      endverse__lte=endverse)
        cfs |= CrossRef.objects.filter(startverse__gte=startverse,
                                       endverse__gte=endverse,
                                       startverse__lte=endverse)
        cfs |= CrossRef.objects.filter(startverse__lte=startverse,
                                       endverse__lte=endverse,
                                       endverse__gte=startverse)

        # get tidbits where a cross ref is completely contained in the given range (more precise)
        cfs = CrossRef.objects.filter(startverse__gte=startverse,
                                      endverse__lte=endverse)

        # get tidbit entries for each cf

        from django.db.models.query import EmptyQuerySet
        tidbits = EmptyQuerySet()
        for cf in cfs:
            tidbits |= cf.tidbit_set.all()
        tidbits = tidbits.distinct()
    else:
        return HttpResponseRedirect(reverse('tidbits:home'))
    c = {
        'filter_criteria': passage_ref,
        'filter_count': tidbits.count(),
        'tidbits': paginate_tidbits(request, tidbits),
        'total_count': Tidbit.objects.all().count()
    }
    return render_to_response("tidbits_home.html",
                              c,
                              context_instance=RequestContext(request))
Beispiel #6
0
def tidbits_by_passage(request, user=None):
    if request.method == 'GET':
        passage_ref = request.GET['passage_ref']
        try:
            startverse, endverse = CrossRef.objects.parse_reference(passage_ref)
        except Exception as e:
            # TODO: make this neater
            return HttpResponse(str(e.args[0]))

        # regex = re.compile("(?P<book>([12]?[A-Za-z ]+[A-Za-z]))( ?(?P<start_chapter>[0-9]+)((:(?P<start_verse>[0-9]+))? ?(- ?(?P<end_chp_or_verse>[0-9]+)(:(?P<end_verse>[0-9]+))?)?)?)?$")
        # matches = regex.search(passage_ref)
        # if matches == None:
        #     # TODO: make this neater
        #     return HttpResponse("Invalid passage reference: " + passage_ref)
        # groups = matches.groupdict()
        # book = groups['book']
        # startchp = groups['start_chapter']
        # startvs = groups['start_verse']
        # endchporvs = groups['end_chp_or_verse']
        # endvs = groups['end_verse']
        # cfs = None
        
        # if not startchp:
        #     startverse = Verse.objects.filter(book__iexact=book).order_by('id')[:1]
        #     if startverse.count() == 0:
        #         return HttpResponse(book + " is not valid!")
        #     endverse = Verse.objects.filter(book__iexact=book).order_by('-id')[:1]
        #     cfs = CrossRef.objects.filter(startverse__gte=startverse, endverse__lte=endverse)
        # elif not startvs:
        #     startverse = Verse.objects.filter(
        #                     book__iexact=book, 
        #                     chapter_ref=int(startchp), 
        #                     verse_ref=1
        #                 ).order_by('id')[:1]
        #     if startverse.count() == 0:
        #         return HttpResponse(book + " " + int(startchp) + " is not valid!")
        #     endverse = Verse.objects.filter(
        #                     book__iexact=book,
        #                     chapter_ref=int(startchp)
        #                 ).order_by('-id')[:1]
        #     cfs = CrossRef.objects.filter(startverse__gte=startverse, endverse__lte=endverse)
        # elif not endchporvs:
        #     endchp = startchp
        #     endvs = startvs
        # elif not endvs:
        #     endvs = endchporvs
        #     endchp = startchp
        # else:
        #     endchp = endchporvs

        # startverse = Verse.objects.get(book__istartswith=book,
        #                                 chapter_ref=int(startchp),
        #                                 verse_ref=int(startvs))
        # endverse = Verse.objects.get(book__istartswith=book,
        #                                 chapter_ref=int(endchp),
        #                                 verse_ref=int(endvs))
        # if startverse.id > endverse.id:
        #     # TODO: handle this
        #     return HttpResponse("Start verse " + startverse + " must come before end verse " + endverse)

        # get tidbits where a cross ref intersects with a given range (more general)
        cfs = CrossRef.objects.filter(
                                startverse__gte=startverse, 
                                endverse__lte=endverse
                            )
        cfs |= CrossRef.objects.filter(
                                startverse__gte=startverse, 
                                endverse__gte=endverse, 
                                startverse__lte=endverse
                            )
        cfs |= CrossRef.objects.filter(
                                startverse__lte=startverse, 
                                endverse__lte=endverse, 
                                endverse__gte=startverse
                            )

        # get tidbits where a cross ref is completely contained in the given range (more precise)
        cfs = CrossRef.objects.filter(
                                startverse__gte=startverse, 
                                endverse__lte=endverse
                            )

        # get tidbit entries for each cf

        from django.db.models.query import EmptyQuerySet
        tidbits = EmptyQuerySet()
        for cf in cfs:
            tidbits |= cf.tidbit_set.all()
        tidbits = tidbits.distinct()
    else:
        return HttpResponseRedirect(reverse('tidbits:home'))
    c = {
        'filter_criteria': passage_ref,
        'filter_count': tidbits.count(),
        'tidbits': paginate_tidbits(request, tidbits),
        'total_count': Tidbit.objects.all().count()
    }
    return render_to_response("tidbits_home.html", c, context_instance=RequestContext(request))
Beispiel #7
0
def voters_email(request, election, poll=None, voter_uuid=None):
    user = request.admin

    TEMPLATES = [("vote", _("Time to Vote")), ("info", _("Additional Info"))]

    default_template = "vote"

    if not election.any_poll_feature_can_send_voter_mail:
        raise PermissionDenied("34")

    if not election.any_poll_feature_can_send_voter_booth_invitation:
        TEMPLATES.pop(0)
        default_template = "info"

    if election.voting_extended_until and not election.voting_ended_at:
        TEMPLATES.append(("extension", _("Voting end date extended")))

    template = request.REQUEST.get("template", default_template)

    if not template in [t[0] for t in TEMPLATES]:
        raise Exception("bad template")

    polls = [poll]
    if not poll:
        polls = election.polls_by_link_id

    voter = None
    if voter_uuid:
        try:
            if poll:
                voter = get_object_or_404(Voter, uuid=voter_uuid, poll=poll)
            else:
                voter = get_object_or_404(Voter, uuid=voter_uuid, election=election)
        except Voter.DoesNotExist:
            raise PermissionDenied("35")
        if not voter:
            url = election_reverse(election, "index")
            return HttpResponseRedirect(url)

    election_url = election.get_absolute_url()

    default_subject = render_to_string("email/%s_subject.txt" % template, {"custom_subject": "&lt;SUBJECT&gt;"})

    default_body = render_to_string(
        "email/%s_body.txt" % template,
        {
            "election": election,
            "election_url": election_url,
            "custom_subject": default_subject,
            "custom_message": "&lt;BODY&gt;",
            "voter": {
                "vote_hash": "<SMART_TRACKER>",
                "name": "<VOTER_NAME>",
                "voter_name": "<VOTER_NAME>",
                "voter_surname": "<VOTER_SURNAME>",
                "voter_login_id": "<VOTER_LOGIN_ID>",
                "voter_password": "******",
                "audit_passwords": "1",
                "get_audit_passwords": ["pass1", "pass2", "..."],
                "get_quick_login_url": "<VOTER_LOGIN_URL>",
                "poll": poll,
                "election": election,
            },
        },
    )

    q_param = request.GET.get("q", None)

    filtered_voters = election.voters.filter()
    if poll:
        filtered_voters = poll.voters.filter()

    if not q_param:
        filtered_voters = EmptyQuerySet()
    else:
        voters_filters = get_filters(
            q_param, VOTER_TABLE_HEADERS, VOTER_SEARCH_FIELDS, VOTER_BOOL_KEYS_MAP, VOTER_EXTRA_HEADERS
        )
        filtered_voters = filtered_voters.filter(voters_filters)

        if not filtered_voters.count():
            message = _("No voters were found.")
            messages.error(request, message)
            url = election_reverse(election, "polls_list")
            return HttpResponseRedirect(url)

    if request.method == "GET":
        email_form = EmailVotersForm()
        email_form.fields["subject"].initial = dict(TEMPLATES)[template]
        if voter:
            email_form.fields["send_to"].widget = email_form.fields["send_to"].hidden_widget()
    else:
        email_form = EmailVotersForm(request.POST)
        if email_form.is_valid():
            # the client knows to submit only once with a specific voter_id
            voter_constraints_include = None
            voter_constraints_exclude = None
            update_booth_invitation_date = False
            if template == "vote":
                update_booth_invitation_date = True

            if voter:
                voter_constraints_include = {"uuid": voter.uuid}

            # exclude those who have not voted
            if email_form.cleaned_data["send_to"] == "voted":
                voter_constraints_exclude = {"vote_hash": None}

            # include only those who have not voted
            if email_form.cleaned_data["send_to"] == "not-voted":
                voter_constraints_include = {"vote_hash": None}

            for _poll in polls:
                if not _poll.feature_can_send_voter_mail:
                    continue

                if template == "vote" and not _poll.feature_can_send_voter_booth_invitation:
                    continue

                subject_template = "email/%s_subject.txt" % template
                body_template = "email/%s_body.txt" % template
                extra_vars = {
                    "custom_subject": email_form.cleaned_data["subject"],
                    "custom_message": email_form.cleaned_data["body"],
                    "election_url": election_url,
                }
                task_kwargs = {
                    "subject_template": subject_template,
                    "body_template": body_template,
                    "extra_vars": extra_vars,
                    "voter_constraints_include": voter_constraints_include,
                    "voter_constraints_exclude": voter_constraints_exclude,
                    "update_date": True,
                    "update_booth_invitation_date": update_booth_invitation_date,
                    "q_param": q_param,
                }
                log_obj = election
                if poll:
                    log_obj = poll
                if voter:
                    log_obj.logger.info(
                        "Notifying single voter %s, [template: %s, filter: %s]", voter.voter_login_id, template, q_param
                    )
                else:
                    log_obj.logger.info("Notifying voters, [template: %s, filter: %r]", template, q_param)
                tasks.voters_email.delay(_poll.pk, **task_kwargs)

            filters = get_voters_filters_with_constraints(q_param, voter_constraints_include, voter_constraints_exclude)
            send_to = filtered_voters.filter(filters)
            if q_param and not send_to.filter(filters).count():
                msg = "No voters matched your filters. No emails were sent."
                messages.error(request, _(msg))
            else:
                messages.info(request, _("Email sending started"))

            url = election_reverse(election, "polls_list")
            if poll:
                url = poll_reverse(poll, "voters")
            if q_param:
                url += "?q=%s" % urllib.quote_plus(q_param)
            return HttpResponseRedirect(url)

    context = {
        "email_form": email_form,
        "election": election,
        "poll": poll,
        "voter_o": voter,
        "default_subject": default_subject,
        "default_body": default_body,
        "template": template,
        "filtered_voters": filtered_voters,
        "templates": TEMPLATES,
    }
    set_menu("voters", context)
    if not poll:
        set_menu("polls", context)
    return render_template(request, "voters_email", context)