Skip to content

Commit

Permalink
extend send_voter_sms management command
Browse files Browse the repository at this point in the history
introduce ``--voters-mobile-file`` parameter. Accepts a path to a csv
file containing a list of election voter mobiles number in the following
format,

<voter-id>,<email>,<mobile>

if set, the script will validate the contents of the file and send
sms only to the voters extracted from the csv entries. The mobile
number provided in csv file will be used to deliver the sms message.
  • Loading branch information
vinilios committed Jun 19, 2014
1 parent 60ffe5a commit 1df639a
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 12 deletions.
92 changes: 85 additions & 7 deletions helios/management/commands/send_voter_sms.py
Expand Up @@ -8,7 +8,8 @@

from heliosauth.models import *
from heliosauth.auth_systems.password import make_password
from helios.models import Voter
from helios.models import Voter, csv_reader
from zeus import utils

from zeus.models import Institution
from zeus import tasks
Expand All @@ -35,6 +36,11 @@ class Command(BaseCommand):
dest='template',
default=None,
help='Path to the sms message template file'),
make_option('--voters-mobiles-file',
action='store',
dest='mobiles_map_file',
default=None,
help='Path to the voters mobiles csv file'),
make_option('--nodry',
action='store_false',
dest='dry',
Expand Down Expand Up @@ -67,11 +73,16 @@ class Command(BaseCommand):
dest='status',
default=False,
help='Query status of the last sms sent.'),
make_option('--no-vote',
make_option('--voters-not-voted',
action='store_true',
dest='voters_not_voted',
default=False,
help='Exclude voters who have already voted.'),
make_option('--voters-voted',
action='store_true',
dest='no_vote',
dest='voters_voted',
default=False,
help='Exclude voters who have already voted to the poll.'),
help='Exclude voters who haven\'t yet voted.'),
make_option('--send-to',
action='store',
dest='send_to',
Expand All @@ -94,21 +105,30 @@ def handle(self, *args, **options):
send_to = options.get('send_to')
resend = options.get('resend')
status = options.get('status')
voters_voted = options.get('voters_voted')
voters_not_voted = options.get('voters_not_voted')
mobiles_map_file = options.get('mobiles_map_file')

if voters_voted and voters_not_voted:
raise CommandError("Please use only one of --voters-voted/--voters-not-voted")

if not any([euuid, puuid]):
raise CommandError("Please provide election or poll uuid")
raise CommandError("Please provide election or poll uuid.")

if not template:
raise CommandError("Please provide a template")
raise CommandError("Please provide a template file.")

if not os.path.exists(template):
raise CommandError("Template file not found")
raise CommandError("Template file does not exist.")

if dry and not status:
print "Running in dry mode. No messages will be send."

if mobiles_map_file and not os.path.exists(mobiles_map_file):
raise CommandError("Voters mobiles file does not exist.")

voters = Voter.objects.filter()
all_voters = Voter.objects.filter()
tplfd = file(template)
tpl = tplfd.read()
tplfd.close()
Expand All @@ -125,6 +145,50 @@ def handle(self, *args, **options):
if not status:
print "Will send %d messages" % voters.count()

mobiles_map = None
if mobiles_map_file:
mobiles_map = {}
with open(mobiles_map_file, 'r') as f:
data = f.read()
reader = csv_reader(data, min_fields=3, max_fields=4)
for fields in reader:
voter_id = fields[0].strip()
email = fields[1].strip()
mobile = fields[2].strip()

if voter_id in mobiles_map.keys():
raise CommandError(("Duplicate voter id found in mobiles"
" csv file: %d") % int(voter_id))

mobiles_map[voter_id] = {
'mobile': mobile,
'email': email
}
try:
utils.sanitize_mobile_number(mobile)
except:
raise CommandError("Invalid mobile number: %s (%s)" % (
email, mobile
))

voters = voters.filter(voter_login_id__in=mobiles_map.keys())
if voters.count() != len(mobiles_map.keys()):
for voter_id in mobiles_map.keys():
if not all_voters.filter(voter_login_id=voter_id).count():
raise CommandError("Voter id not found in "
"database: %s" % voter_id)
for voter in voters:
voter_id = voter.voter_login_id
email = voter.voter_email
csv_email = mobiles_map[voter_id]['email']
if email != csv_email:
print repr(email), repr(csv_email)
raise CommandError("Voter email does not match the one"
" in database: %s, %s, %s" % (
voter_id,
email,
csv_email))

for voter in voters:
if list:
print voter.voter_email, voter.zeus_string
Expand All @@ -147,13 +211,27 @@ def handle(self, *args, **options):
continue

self.stdout.write("Sending sms to %s " % (voter.zeus_string))

if not resend and voter.last_sms_send_at:
d = voter.last_sms_send_at.strftime("%d/%m/%Y %H:%M:%S")
print "Skipping. Already send at %r" % d
continue

if dry:
print

if mobiles_map:
mapped = mobiles_map.get(voter.voter_login_id)
send_to = send_to or (mapped and mapped.get('mobile'))

if not voter.voter_mobile and not send_to:
print "Skipping. No voter mobile set"
continue

if send_to:
print ("Overriding mobile number. Voter mobile: %s. "
"Will use : %s") % (voter.voter_mobile or "<not-set>",
send_to)
res, error = task(voter.pk, tpl, override_mobile=send_to,
resend=resend, dry=dry)
if res:
Expand Down
4 changes: 2 additions & 2 deletions helios/models.py
Expand Up @@ -43,7 +43,7 @@
from helios.crypto import electionalgs, algs, utils
from helios import utils as heliosutils
from helios import datatypes
from helios import exceptions
from helios import exceptions
from helios.datatypes.djangofield import LDObjectField
from helios.byte_fields import ByteaField
from helios.utils import force_utf8
Expand Down Expand Up @@ -1381,7 +1381,7 @@ def process(self):
for user in poll.election.admins.all():
if user.user_id.startswith('demo_'):
demo_user = True

nr = sum(e.voters.count() for e in user.elections.all())
demo_voters += nr
if demo_voters >= settings.DEMO_MAX_VOTERS and demo_user:
Expand Down
2 changes: 2 additions & 0 deletions sample_sms_confirm_tpl.txt
@@ -0,0 +1,2 @@
ZEUS ELECTION {{ election.name|truncatechars:10 }}: VOTER WITH REG CODE {{ voter.voter_login_id }}
{% if voter.voted %}VOTED {{ voter.cast_votes.count }} TIMES. LAST VOTE AT {{ voter.last_cast_vote.cast_at|date:"d/n/Y H:i:s" }}{% else %}NO VOTE CAST{% endif %}
2 changes: 1 addition & 1 deletion zeus/mobile/locotel.py
Expand Up @@ -20,7 +20,7 @@ class Loco(object):

STATUS_MAP = {
'0': 'Message in queue',
'1': 'Message Send (but still not knowing if delivered)',
'1': 'Message Send (delivery status unknown)',
'2': 'Message Failed',
'3': 'Message Delivered to Terminal',
'4': 'Not sent'
Expand Down
4 changes: 2 additions & 2 deletions zeus/tasks.py
Expand Up @@ -276,7 +276,7 @@ def poll_compute_results(poll_id):
def send_voter_sms(voter_id, tpl, override_mobile=None, resend=False,
dry=True):
voter = Voter.objects.get(pk=voter_id)
if not voter.voter_mobile:
if not voter.voter_mobile and not override_mobile:
raise Exception("Voter mobile field not set")

client = mobile.get_client()
Expand Down Expand Up @@ -313,7 +313,7 @@ def send_voter_sms(voter_id, tpl, override_mobile=None, resend=False,
print "MESSAGE (%d) :" % len(message)
print message
print 10 * "-"
sent, error = True, "FAKE_ID"
sent, error_or_code = True, "FAKE_ID"
else:
# call to the API
poll = voter.poll
Expand Down

0 comments on commit 1df639a

Please sign in to comment.