def add_edit(request, election, poll=None): if not poll and not election.feature_can_add_poll: raise PermissionDenied if poll and not poll.feature_can_edit: raise PermissionDenied if election.linked_polls and 'batch_file' in request.FILES: return _add_batch(request, election) if poll: oldname = poll.name if request.method == "POST": form = PollForm(request.POST, instance=poll, election=election) if form.is_valid(): new_poll = form.save() if form.has_changed(): new_poll.logger.info("Poll updated %r" % form.changed_data) message = _("Poll updated successfully") messages.success(request, message) newname = new_poll.name # log poll edit/creation if poll: if oldname != newname: poll.logger.info("Renamed from %s to %s", oldname, newname) else: new_poll.logger.info("Poll created") url = election_reverse(election, 'polls_list') return redirect(url) if request.method == "GET": form = PollForm(instance=poll, election=election) context = {'election': election, 'poll': poll, 'form': form} set_menu('polls', context) if poll: set_menu('edit_poll', context) tpl = "election_poll_add_or_edit" return render_template(request, tpl, context)
def audited_ballots(request, election, poll): vote_hash = request.GET.get('vote_hash', None) if vote_hash: b = get_object_or_404(AuditedBallot, poll=poll, vote_hash=vote_hash) b = AuditedBallot.objects.get(poll=poll, vote_hash=request.GET['vote_hash']) return HttpResponse(b.raw_vote, content_type="text/plain") audited_ballots = AuditedBallot.objects.filter(is_request=False, poll=poll) voter = None if request.zeususer.is_voter: voter = request.voter voter_audited_ballots = [] if voter: voter_audited_ballots = AuditedBallot.objects.filter(poll=poll, is_request=False, voter=voter) context = { 'election': election, 'audited_ballots': audited_ballots, 'voter_audited_ballots': voter_audited_ballots, 'poll': poll, 'per_page': 50 } set_menu('audited_ballots', context) return render_template(request, 'election_poll_audited_ballots', context)
def index(request, election, poll=None): user = request.zeususer if poll: election_url = poll.get_absolute_url() else: election_url = election.get_absolute_url() booth_url = None linked_booth_urls = [] if poll: booth_url = poll.get_booth_url(request) if poll.has_linked_polls and user.is_voter: for p in poll.linked_polls: if p == poll: continue try: voter = \ p.voters.get(voter_login_id=user._user.voter_login_id) except Exception as e: continue burl = reverse('election_poll_voter_booth_linked_login', args=( election.uuid, poll.uuid, voter.uuid, )) burl = burl + "?link-to=%s" % p.uuid linked_booth_urls.append((p.name, burl, voter.cast_at)) voter = None votes = None if user.is_voter: # cast any votes? voter = request.voter votes = voter.get_cast_votes() if election.frozen_at: voter.last_visit = datetime.datetime.now() voter.save() else: votes = None trustees = election.trustees.filter() context = { 'election': election, 'poll': poll, 'trustees': trustees, 'user': user, 'votes': votes, 'election_url': election_url, 'booth_url': booth_url, 'linked_booth_urls': linked_booth_urls } if poll: context['poll'] = poll set_menu('election', context) return render_template(request, 'election_view', context)
def questions_update_view(self, request, election, poll): from zeus.utils import poll_reverse from zeus.forms import PartyForm, DEFAULT_ANSWERS_COUNT, \ MAX_QUESTIONS_LIMIT extra = 1 if poll.questions_data: extra = 0 questions_formset = formset_factory(PartyForm, extra=extra, can_delete=True, can_order=True) if request.method == 'POST': formset = questions_formset(request.POST) if formset.is_valid(): questions_data = [] for question in formset.cleaned_data: if not question: continue # force sort of answers by extracting index from answer key. # cast answer index to integer, otherwise answer_10 would # be placed before answer_2 answer_index = lambda a: int(a[0].replace('answer_', '')) isanswer = lambda a: a[0].startswith('answer_') answer_values = list( filter(isanswer, iter(question.items()))) sorted_answers = sorted(answer_values, key=answer_index) answers = [x[1] for x in sorted_answers] question['answers'] = answers for k in list(question.keys()): if k in ['DELETE', 'ORDER']: del question[k] questions_data.append(question) poll.questions_data = questions_data poll.update_answers() poll.logger.info("Poll ballot updated") poll.save() url = poll_reverse(poll, 'questions') return HttpResponseRedirect(url) else: formset = questions_formset(initial=poll.questions_data) context = { 'default_answers_count': DEFAULT_ANSWERS_COUNT, 'formset': formset, 'max_questions_limit': MAX_QUESTIONS_LIMIT, 'election': election, 'poll': poll, 'module': self } set_menu('questions', context) tpl = 'election_modules/parties/election_poll_questions_manage' return render_template(request, tpl, context)
def home(request, election, trustee): context = {'election': election, 'trustee': trustee} if not trustee.public_key: url = election_reverse(election, 'trustee_keygen') return HttpResponseRedirect(url) set_menu('trustee', context) return render_template(request, 'election_trustee_home', context)
def voters_list(request, election, poll): # for django pagination support page = int(request.GET.get('page', 1)) limit = int(request.GET.get('limit', 10)) q_param = request.GET.get('q', '') default_voters_per_page = getattr(settings, 'ELECTION_VOTERS_PER_PAGE', 100) voters_per_page = request.GET.get('limit', default_voters_per_page) try: voters_per_page = int(voters_per_page) except: voters_per_page = default_voters_per_page order_by = request.GET.get('order', 'voter_login_id') order_type = request.GET.get('order_type', 'desc') module = election.get_module() table_headers = copy.copy(module.get_voters_list_headers(request)) if order_by not in table_headers: order_by = 'voter_login_id' if not poll.voters.filter(voter_weight__gt=1).count(): table_headers.pop('voter_weight') display_weight_col = 'voter_weight' in table_headers validate_hash = request.GET.get('vote_hash', "").strip() hash_invalid = None hash_valid = None if (order_type == 'asc') or (order_type is None): voters = Voter.objects.filter(poll=poll).annotate( cast_votes__id=Max('cast_votes__id')).order_by(order_by) else: order_by = '-%s' % order_by voters = Voter.objects.filter(poll=poll).annotate( cast_votes__id=Max('cast_votes__id')).order_by(order_by) voters = module.filter_voters(voters, q_param, request) voters_count = Voter.objects.filter(poll=poll).count() voted_count = poll.voters_cast_count() nr_voters_excluded = voters.excluded().count() context = { 'election': election, 'poll': poll, 'page': page, 'voters': voters, 'voters_count': voters_count, 'voted_count': voted_count, 'q': q_param, 'voters_list_count': voters.count(), 'voters_per_page': voters_per_page, 'display_weight_col': display_weight_col, 'voter_table_headers': table_headers, 'voter_table_headers_iter': iter(table_headers.items()), 'nr_voters_excluded': nr_voters_excluded, } set_menu('voters', context) return render_template(request, 'election_poll_voters_list', context)
def questions_update_view(self, request, election, poll): from zeus.utils import poll_reverse from zeus.forms import PartyForm, DEFAULT_ANSWERS_COUNT, \ MAX_QUESTIONS_LIMIT extra = 1 if poll.questions_data: extra = 0 questions_formset = formset_factory(PartyForm, extra=extra, can_delete=True, can_order=True) if request.method == 'POST': formset = questions_formset(request.POST) if formset.is_valid(): questions_data = [] for question in formset.cleaned_data: if not question: continue # force sort of answers by extracting index from answer key. # cast answer index to integer, otherwise answer_10 would # be placed before answer_2 answer_index = lambda a: int(a[0].replace('answer_', '')) isanswer = lambda a: a[0].startswith('answer_') answer_values = filter(isanswer, question.iteritems()) sorted_answers = sorted(answer_values, key=answer_index) answers = [x[1] for x in sorted_answers] question['answers'] = answers for k in question.keys(): if k in ['DELETE', 'ORDER']: del question[k] questions_data.append(question) poll.questions_data = questions_data poll.update_answers() poll.logger.info("Poll ballot updated") poll.save() url = poll_reverse(poll, 'questions') return HttpResponseRedirect(url) else: formset = questions_formset(initial=poll.questions_data) context = { 'default_answers_count': DEFAULT_ANSWERS_COUNT, 'formset': formset, 'max_questions_limit': MAX_QUESTIONS_LIMIT, 'election': election, 'poll': poll, 'module': self } set_menu('questions', context) tpl = 'election_modules/parties/election_poll_questions_manage' return render_template(request, tpl, context)
def results(request, election, poll): if not request.zeususer.is_admin and not poll.feature_public_results: raise PermissionDenied('41') if not poll.get_module().display_poll_results: url = election_reverse(election, 'index') return HttpResponseRedirect(url) context = {'poll': poll, 'election': election} set_menu('results', context) return render_template(request, 'election_poll_results', context)
def add_or_update(request, election=None): user = request.admin institution = user.institution if request.method == "GET": election_form = ElectionForm(user, institution, instance=election, lang=request.LANGUAGE_CODE) else: election_form = ElectionForm(user, institution, request.POST, instance=election) if election_form.is_valid(): creating = election is None with transaction.atomic(): election = election_form.save() if not election.admins.filter(pk=user.pk).count(): election.admins.add(user) if election_form.creating: election.logger.info("Election created") msg = "New election created" subject = "New Zeus election" election.notify_admins(msg=msg, subject=subject) if not election.has_helios_trustee(): election.generate_trustee() if election.polls.count() == 0: url = election_reverse(election, 'polls_add') else: url = election_reverse(election, 'index') if election.voting_extended_until: subject = "Voting extension" msg = "Voting end date extended" election.notify_admins(msg=msg, subject=subject) election.zeus.compute_election_public() election.logger.info("Public key updated") hook_url = None if creating: hook_url = election.get_module().run_hook('post_create') else: hook_url = election.get_module().run_hook('post_update') return HttpResponseRedirect(hook_url or url) context = {'election_form': election_form, 'election': election} set_menu('election_edit', context) tpl = "election_new" if election and election.pk: tpl = "election_edit" return render_template(request, tpl, context)
def questions(request, election, poll): module = poll.get_module() if request.zeususer.is_admin: if not module.questions_set() and poll.feature_can_manage_questions: url = poll_reverse(poll, 'questions_manage') return HttpResponseRedirect(url) preview_booth_url = poll.get_booth_url(request, preview=True) context = { 'election': election, 'poll': poll, 'questions': questions, 'module': poll.get_module(), 'preview_booth_url': preview_booth_url } set_menu('questions', context) tpl = getattr(module, 'questions_list_template', 'election_poll_questions') return render_template(request, tpl, context)
def questions_update_view(self, request, election, poll): from zeus.utils import poll_reverse from zeus.forms import DEFAULT_ANSWERS_COUNT, \ MAX_QUESTIONS_LIMIT extra = 1 if poll.questions_data: extra = 0 questions_formset = self.questions_formset(extra) if request.method == 'POST': formset = questions_formset(request.POST, request.FILES, initial=poll.questions_data) should_submit = not request.FILES and formset.is_valid() if should_submit: cleaned_data = formset.cleaned_data questions_data = self.extract_question_data(cleaned_data) poll.questions_data = questions_data poll.update_answers() poll.logger.info("Poll ballot updated") self.update_poll_params(poll, formset.cleaned_data) poll.save() url = poll_reverse(poll, 'questions') return HttpResponseRedirect(url) else: formset = questions_formset(initial=poll.questions_data) context = { 'default_answers_count': DEFAULT_ANSWERS_COUNT, 'formset': formset, 'max_questions_limit': self.max_questions_limit or MAX_QUESTIONS_LIMIT, 'election': election, 'poll': poll, 'module': self } set_menu('questions', context) tpl = f'election_modules/{self.module_id}/election_poll_questions_manage' return render_template(request, tpl, context)
def trustees_list(request, election): trustees = election.trustees.filter(election=election, secret_key__isnull=True).order_by('pk') # TODO: can we move this in a context processor # or middleware ??? voter = None poll = None if getattr(request, 'voter', None): voter = request.voter poll = voter.poll context = { 'election': election, 'poll': poll, 'voter': voter, 'trustees': trustees } set_menu('trustees', context) return render_template(request, 'election_trustees_list', context)
def questions_update_view(self, request, election, poll): from zeus.utils import poll_reverse from zeus.forms import QuestionForm, DEFAULT_ANSWERS_COUNT, \ MAX_QUESTIONS_LIMIT extra = 1 if poll.questions_data: extra = 0 questions_formset = self.questions_formset(extra) if request.method == 'POST': formset = questions_formset(request.POST) if formset.is_valid(): cleaned_data = formset.cleaned_data questions_data = self.extract_question_data(cleaned_data) poll.questions_data = questions_data poll.update_answers() poll.logger.info("Poll ballot updated") poll.save() url = poll_reverse(poll, 'questions') return HttpResponseRedirect(url) else: formset = questions_formset(initial=poll.questions_data) context = { 'default_answers_count': DEFAULT_ANSWERS_COUNT, 'formset': formset, 'max_questions_limit': MAX_QUESTIONS_LIMIT, 'election': election, 'poll': poll, 'module': self } set_menu('questions', context) tpl = 'election_modules/simple/election_poll_questions_manage' return render_template(request, tpl, context)
def test_set_menu(): menu = 'menu' ctx = {} utils.set_menu(menu, ctx) assert ctx['menu_active'] == menu
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' 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) if voter.excluded_at: TEMPLATES.pop(0) default_template = 'info' if election.voting_extended_until and not election.voting_ended_at: if not voter or (voter and not voter.excluded_at): TEMPLATES.append(('extension', _('Voting end date extended'))) if request.method == 'POST': template = request.POST.get('template', default_template) else: template = request.GET.get('template', default_template) if template not in [t[0] for t in TEMPLATES]: raise Exception("bad template") election_url = election.get_absolute_url() default_subject = render_to_string('email/%s_subject.txt' % template, {'custom_subject': "<SUBJECT>"}) tpl_context = { 'election': election, 'election_url': election_url, 'custom_subject': default_subject, 'custom_message': '<BODY>', 'custom_message_sms': '<SMS_BODY>', 'SECURE_URL_HOST': settings.SECURE_URL_HOST, 'voter': { 'vote_hash': '<SMART_TRACKER>', 'name': '<VOTER_NAME>', 'voter_name': '<VOTER_NAME>', 'voter_surname': '<VOTER_SURNAME>', 'voter_login_id': '<VOTER_LOGIN_ID>', 'voter_password': '******', 'login_code': '<VOTER_LOGIN_CODE>', 'audit_passwords': '1', 'get_audit_passwords': ['pass1', 'pass2', '...'], 'get_quick_login_url': '<VOTER_LOGIN_URL>', 'poll': poll, 'election': election } } default_body = render_to_string('email/%s_body.txt' % template, tpl_context) default_sms_body = render_to_string('sms/%s_body.txt' % template, tpl_context) 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 = filtered_voters.none() else: filtered_voters = election.get_module().filter_voters( filtered_voters, q_param, request) 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(election, template) email_form.fields['email_subject'].initial = dict(TEMPLATES)[template] if voter: email_form.fields['send_to'].widget = \ email_form.fields['send_to'].hidden_widget() else: email_form = EmailVotersForm(election, template, 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 body_template_sms = 'sms/%s_body.txt' % template contact_method = email_form.cleaned_data['contact_method'] extra_vars = { 'SECURE_URL_HOST': settings.SECURE_URL_HOST, 'custom_subject': email_form.cleaned_data['email_subject'], 'custom_message': email_form.cleaned_data['email_body'], 'custom_message_sms': email_form.cleaned_data['sms_body'], 'election_url': election_url, } task_kwargs = { 'contact_id': template, 'notify_once': email_form.cleaned_data.get('notify_once'), 'subject_template_email': subject_template, 'body_template_email': body_template, 'body_template_sms': body_template_sms, 'contact_methods': contact_method.split(":"), 'template_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' % six.moves.urllib.parse.quote_plus(q_param) return HttpResponseRedirect(url) else: message = _("Something went wrong") messages.error(request, message) if election.sms_enabled and election.sms_data.left <= 0: messages.warning(request, _("No SMS deliveries left.")) context = { 'email_form': email_form, 'election': election, 'poll': poll, 'voter_o': voter, 'default_subject': default_subject, 'default_body': default_body, 'default_sms_body': default_sms_body, 'sms_enabled': election.sms_enabled, '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)
def polls_list(request, election): polls = election.polls.filter() context = {'polls': polls, 'election': election} set_menu('polls', context) return render_template(request, "election_polls_list", context)
def voters_upload(request, election, poll): common_context = { 'election': election, 'poll': poll, 'encodings': ENCODINGS } set_menu('voters', common_context) if request.method == "POST": preferred_encoding = request.POST.get('encoding', None) if preferred_encoding not in dict(ENCODINGS): messages.error(request, _("Invalid encoding")) url = poll_reverse(poll, 'voters_upload') return HttpResponseRedirect(url) else: common_context['preferred_encoding'] = preferred_encoding if bool(request.POST.get('confirm_p', 0)): # launch the background task to parse that file voter_file_id = request.session.get('voter_file_id', None) process_linked = request.session.get('no_link', False) is False if not voter_file_id: messages.error(request, _("Invalid voter file id")) url = poll_reverse(poll, 'voters') return HttpResponseRedirect(url) try: voter_file = VoterFile.objects.get(pk=voter_file_id) try: voter_file.process(process_linked, preferred_encoding=preferred_encoding) except (exceptions.VoterLimitReached, exceptions.DuplicateVoterID, ValidationError) as e: messages.error(request, e.message) voter_file.delete() url = poll_reverse(poll, 'voters') return HttpResponseRedirect(url) poll.logger.info("Processing voters upload") except VoterFile.DoesNotExist: pass except KeyError: pass if 'no_link' in request.session: del request.session['no_link'] if 'voter_file_id' in request.session: del request.session['voter_file_id'] url = poll_reverse(poll, 'voters') return HttpResponseRedirect(url) else: if 'voter_file_id' in request.session: del request.session['voter_file_id'] # we need to confirm voters = [] error = None invalid_emails = [] def _email_validate(eml, line): try: validate_email(eml) except ValidationError: invalid_emails.append((eml, line)) return True if 'voters_file' in request.FILES: voters_file = request.FILES['voters_file'] voter_file_obj = poll.add_voters_file(voters_file) # import the first few lines to check invalid_emails = [] try: voters = [ v for v in voter_file_obj.itervoters( email_validator=_email_validate, preferred_encoding=preferred_encoding) ] except ValidationError as e: if hasattr(e, 'messages') and e.messages: error = "".join(e.messages) else: error = "error." except Exception as e: logging.exception('error reading voter file') voter_file_obj.delete() error = str(e) if 'voter_file_id' in request.session: del request.session['voter_file_id'] messages.error(request, error) url = poll_reverse(poll, 'voters_upload') return HttpResponseRedirect(url) if len(invalid_emails): error = _("Enter a valid email address. " "<br />") for email, line in invalid_emails: error += "<br />" + "line %d: %s " % (line, escape(email)) error = mark_safe(error) else: error = _("No file uploaded") if not error: request.session['voter_file_id'] = voter_file_obj.id count = len(voters) context = common_context context.update({'voters': voters, 'count': count, 'error': error}) return render_template(request, 'election_poll_voters_upload_confirm', context) else: if 'voter_file_id' in request.session: del request.session['voter_file_id'] no_link = bool(request.GET.get("no-link", False)) request.session['no_link'] = no_link return render_template(request, 'election_poll_voters_upload', common_context)
def questions_update_view(self, request, election, poll): from zeus.utils import poll_reverse from zeus.forms import StvForm, DEFAULT_ANSWERS_COUNT if not poll.questions_data: poll.questions_data = [{}] poll.questions_data[0]['departments_data'] = election.departments initial = poll.questions_data extra = 1 if poll.questions_data: extra = 0 questions_formset = formset_factory(StvForm, extra=extra, can_delete=True, can_order=True) if request.method == 'POST': formset = questions_formset(request.POST, initial=initial) if formset.is_valid(): questions_data = [] for question in formset.cleaned_data: if not question: continue # force sort of answers by extracting index from answer key. # cast answer index to integer, otherwise answer_10 would # be placed before answer_2 answer_index = lambda a: int(a[0].replace('answer_', '')) isanswer = lambda a: a[0].startswith('answer_') answer_values = list( filter(isanswer, iter(question.items()))) sorted_answers = sorted(answer_values, key=answer_index) answers = [json.loads(x[1])[0] for x in sorted_answers] departments = [json.loads(x[1])[1] for x in sorted_answers] final_answers = [] for a, d in zip(answers, departments): final_answers.append(a + ':' + d) question['answers'] = final_answers for k in list(question.keys()): if k in ['DELETE', 'ORDER']: del question[k] questions_data.append(question) poll.questions_data = questions_data poll.update_answers() poll.logger.info("Poll ballot updated") poll.eligibles_count = int( formset.cleaned_data[0]['eligibles']) poll.has_department_limit = formset.cleaned_data[0][ 'has_department_limit'] poll.department_limit = int( formset.cleaned_data[0]['department_limit']) poll.save() url = poll_reverse(poll, 'questions') return HttpResponseRedirect(url) else: formset = questions_formset(initial=initial) context = { 'default_answers_count': DEFAULT_ANSWERS_COUNT, 'formset': formset, 'max_questions_limit': 1, 'election': election, 'poll': poll, 'module': self } set_menu('questions', context) tpl = 'election_modules/stv/election_poll_questions_manage' return render_template(request, tpl, context)