diff --git a/helios/models.py b/helios/models.py index db27f5a1ca37c569a8252812ec9177e690809f5d..f5e384e436f7eee9827b3d930d3021f410c9e166 100644 --- a/helios/models.py +++ b/helios/models.py @@ -16,6 +16,7 @@ import bleach import unicodecsv from django.conf import settings from django.db import models, transaction +from validate_email import validate_email from helios import datatypes from helios import utils @@ -765,64 +766,74 @@ class VoterFile(models.Model): for voter_fields in reader: # bad line - if len(voter_fields) < 1: + if len(voter_fields) < 2: continue - - return_dict = {'voter_id': voter_fields[0].strip()} - if len(voter_fields) > 1: - return_dict['email'] = voter_fields[1].strip() - else: - # assume single field means the email is the same field - return_dict['email'] = voter_fields[0].strip() + voter_type = voter_fields[0].strip() + voter_id = voter_fields[1].strip() + if not voter_type in AUTH_SYSTEMS: + raise Exception("invalid voter type '%s' for voter id '%s'" % (voter_type, voter_id)) + + # default to having email be the same as voter_id + voter_email = voter_id if len(voter_fields) > 2: - return_dict['name'] = voter_fields[2].strip() - else: - return_dict['name'] = return_dict['email'] + # but if it's supplied, it will be the 3rd field. + voter_email = voter_fields[2].strip() + if voter_type == "password" and not validate_email(voter_email): + raise Exception("invalid voter email '%s' for voter id '%s'" % (voter_email, voter_id)) + + # same thing for voter display name. + voter_name = voter_email + if len(voter_fields) > 3: + # which is supplied as the 4th field if known. + voter_name = voter_fields[3].strip() + + yield { + 'voter_type': voter_type, + 'voter_id': voter_id, + 'email': voter_email, + 'name': voter_name, + } - yield return_dict if close: voter_stream.close() - + def process(self): self.processing_started_at = datetime.datetime.utcnow() self.save() - election = self.election - last_alias_num = election.last_alias_num - - num_voters = 0 - new_voters = [] - for voter in self.itervoters(): - num_voters += 1 - - # does voter for this user already exist - existing_voter = Voter.get_by_election_and_voter_id(election, voter['voter_id']) - - # create the voter - if not existing_voter: - voter_uuid = str(uuid.uuid4()) - existing_voter = Voter(uuid= voter_uuid, user = None, voter_login_id = voter['voter_id'], - voter_name = voter['name'], voter_email = voter['email'], election = election) - existing_voter.generate_password() - new_voters.append(existing_voter) - existing_voter.save() - - if election.use_voter_aliases: - voter_alias_integers = list(range(last_alias_num+1, last_alias_num+1+num_voters)) - random.shuffle(voter_alias_integers) - for i, voter in enumerate(new_voters): - voter.alias = 'V%s' % voter_alias_integers[i] - voter.save() + voters = list(self.itervoters()) + self.num_voters = len(voters) + random.shuffle(voters) + + for voter in voters: + if voter['voter_type'] == 'password': + # does voter for this user already exist + existing_voter = Voter.get_by_election_and_voter_id(self.election, voter['voter_id']) + if existing_voter: + continue + # create the voter + voter_uuid = str(uuid.uuid4()) + new_voter = Voter(uuid=voter_uuid, user = None, voter_login_id = voter['voter_id'], + voter_name = voter['name'], voter_email = voter['email'], election = self.election) + new_voter.generate_password() + if election.use_voter_aliases: + utils.lock_row(Election, election.id) + alias_num = election.last_alias_num + 1 + new_voter.alias = "V%s" % alias_num + new_voter.save() + else: + user, _ = User.objects.get_or_create(user_type=voter['voter_type'], user_id=voter['voter_id'], defaults = {'name': voter['voter_id'], 'info': {}, 'token': None}) + existing_voter = Voter.get_by_election_and_user(self.election, user) + if not existing_voter: + Voter.register_user_in_election(user, self.election) - self.num_voters = num_voters self.processing_finished_at = datetime.datetime.utcnow() self.save() - return num_voters + return self.num_voters - class Voter(HeliosModel): election = models.ForeignKey(Election, on_delete=models.CASCADE) diff --git a/helios/templates/voters_upload.html b/helios/templates/voters_upload.html index 908c147f124f4c342b034b6845bd406f3e5b8048..116ed7ec5392951a13930e99d23ecc61460a0bed 100644 --- a/helios/templates/voters_upload.html +++ b/helios/templates/voters_upload.html @@ -4,22 +4,28 @@ <h2 class="title">{{election.name}} — Bulk Upload Voters <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> <form method="post" action="" id="upload_form" enctype="multipart/form-data"> - <p> +<p> If you would like to specify your list of voters by name and email address,<br /> you can bulk upload a list of such voters here.<br /><br /> Please prepare a text file of comma-separated values with the fields: </p> <pre> - <unique_id>,<email>,<full name> + password,<unique_id>,<email>,<full name> +</pre> +<p> +or +</p> +<pre> + github,<username> </pre> <p> For example: </p> <pre> - benadida,ben@adida.net,Ben Adida - bobsmith,bob@acme.org,Bob Smith + password,bobsmith,bob@acme.org,Bob Smith + github,benadida ... </pre> diff --git a/helios/templates/voters_upload_confirm.html b/helios/templates/voters_upload_confirm.html index 911cb259d9d321e6bd0cc69ddc9cde95acb7320c..978aecee2bc327efd16c2c2b6d96c93a11b6164a 100644 --- a/helios/templates/voters_upload_confirm.html +++ b/helios/templates/voters_upload_confirm.html @@ -8,9 +8,9 @@ You have uploaded a file of voters. The first few rows of this file are: </p> <table> -<tr><th>Voter Login</th><th>Email Address</th><th>Name</th></tr> +<tr><th>Voter Type</th><th>Voter Login</th><th>Email Address</th><th>Name</th></tr> {% for v in voters %} -<tr><td>{{v.voter_id}}</td><td>{{v.email}}</td><td>{{v.name}}</td></tr> +<tr><td>{{v.voter_type}}</td><td>{{v.voter_id}}</td><td>{{v.email}}</td><td>{{v.name}}</td></tr> {% endfor %} </table> diff --git a/helios/views.py b/helios/views.py index 3b19d8ca680f2e5cba9082758d25784472de2770..9be3a316677d9459ed5f953ad579e950b82ec514 100644 --- a/helios/views.py +++ b/helios/views.py @@ -17,7 +17,6 @@ from django.core.paginator import Paginator from django.db import transaction, IntegrityError from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseForbidden from django.urls import reverse -from validate_email import validate_email import helios_auth.url_names as helios_auth_urls from helios import utils, VOTERS_EMAIL, VOTERS_UPLOAD, url_names @@ -1268,10 +1267,8 @@ def voters_eligibility(request, election): @election_admin() def voters_upload(request, election): """ - Upload a CSV of password-based voters with - voter_id, email, name - - name and email are needed only if voter_type is static + Upload a CSV of voters with + voter_type, voter_id, optional_additional_params (e.g. email, name) """ ## TRYING this: allowing voters upload by admin when election is frozen @@ -1301,13 +1298,11 @@ def voters_upload(request, election): # import the first few lines to check try: voters = [v for v in voter_file_obj.itervoters()][:5] - except: + if len(voters) == 0: + raise Exception("no valid lines found in voter file") + except Exception as e: voters = [] - problems.append("your CSV file could not be processed. Please check that it is a proper CSV file.") - - # check if voter emails look like emails - if False in [validate_email(v['email']) for v in voters]: - problems.append("those don't look like correct email addresses. Are you sure you uploaded a file with email address as second field?") + problems.append("your CSV file could not be processed because %s" % str(e)) return render_template(request, 'voters_upload_confirm', {'election': election, 'voters': voters, 'problems': problems}) else: