Skip to content

Commit

Permalink
✨ Automated Allow/Launch Tally (#363)
Browse files Browse the repository at this point in the history
Parent issue: sequentech/meta#1003
  • Loading branch information
Findeton authored Jan 30, 2025
1 parent 3a9ec7b commit 98519f6
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 18 deletions.
20 changes: 20 additions & 0 deletions iam/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,18 @@ class ScheduledEventsSchema(Schema):
load_default=None,
dump_default=None
)
allow_tally = marshmallow_fields.Nested(
ScheduledEventSchema,
allow_none=True,
load_default=None,
dump_default=None
)
start_tally = marshmallow_fields.Nested(
ScheduledEventSchema,
allow_none=True,
load_default=None,
dump_default=None
)


class OIDCPPublicInfoSchema(Schema):
Expand Down Expand Up @@ -925,11 +937,15 @@ def update_scheduled_events(sender, instance, **kwargs):
default_events = dict(
start_voting=None,
end_voting=None,
allow_tally=None,
start_tally=None,
)

alt_status = dict(
start_voting='start',
end_voting='stop',
allow_tally='allow-tally',
start_tally='tally',
)

events = (instance.scheduled_events
Expand Down Expand Up @@ -1168,6 +1184,10 @@ def create_user_data(sender, instance, created, *args, **kwargs):
('authevent:start_voting:revoked', 'authevent:start_voting:revoked'),
('authevent:end_voting:scheduled', 'authevent:end_voting:scheduled'),
('authevent:end_voting:revoked', 'authevent:end_voting:revoked'),
('authevent:allow_tally:scheduled', 'authevent:allow_tally:scheduled'),
('authevent:allow_tally:revoked', 'authevent:allow_tally:revoked'),
('authevent:start_tally:scheduled', 'authevent:start_tally:scheduled'),
('authevent:start_tally:revoked', 'authevent:start_tally:revoked'),
)

class Action(models.Model):
Expand Down
47 changes: 39 additions & 8 deletions iam/api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import plugins
from authmethods.sms_provider import SMSProvider
from utils import send_codes, generate_access_token_hmac, reproducible_json_dumps
from api import views
from .models import Action, AuthEvent, BallotBox, TallySheet

logger = get_task_logger(__name__)
Expand Down Expand Up @@ -1031,6 +1032,28 @@ def set_public_candidates_task(
)
action.save()

def run_start_tally(
user_id,
auth_event_id,
):
'''
Launches the a ballot box action call in a task. If the election has
children, also launches the call for those.
'''
logger.info(
f'\n\nrun_start_tally_task(user_id={user_id}, auth_event_id={auth_event_id})'
)
user = get_object_or_404(User, pk=user_id)
auth_event = get_object_or_404(AuthEvent, pk=auth_event_id)
elements = auth_event.children_election_info['natural_order'] if auth_event.children_election_info else []
req = {
"children_election_ids": elements,
"force_tally": "force-all",
"mode": "all"
}

views.TallyStatusView.tally_status_post(auth_event_id, req, user)

def run_ballot_box_action(
action_name,
user_id,
Expand Down Expand Up @@ -1173,13 +1196,21 @@ def set_status_task(status, user_id, auth_event_id, parent_auth_event_id=None):
'resume': 'resumed',
}
def set_status_inner(auth_event):
if status in ['allow-tally', 'tally']:
return
auth_event.status = alt_status[status]
auth_event.save()

run_ballot_box_action(
action_name=status,
user_id=user_id,
auth_event_id=auth_event_id,
auth_event_callback_func=set_status_inner,
apply_callback=(status in ['start', 'stop', 'suspend', 'resume'])
)

if 'tally' == status:
run_start_tally(
user_id=user_id,
auth_event_id=auth_event_id,
)
else:
run_ballot_box_action(
action_name=status,
user_id=user_id,
auth_event_id=auth_event_id,
auth_event_callback_func=set_status_inner,
apply_callback=(status in ['start', 'stop', 'suspend', 'resume', 'allow-tally', 'tally'])
)
30 changes: 20 additions & 10 deletions iam/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2903,22 +2903,14 @@ def delete(self, request, pk, ballot_box_pk, tally_sheet_pk):

class TallyStatusView(View):

def post(self, request, pk):
def tally_status_post(pk, req, user):
'''
Launches the tallly in a celery background task. If the
election has children, also launches the tally for them.
'''
# check permissions
permission_required(
request.user,
'AuthEvent',
['edit', 'tally'],
pk
)

# get AuthEvent and parse request json
auth_event = get_object_or_404(AuthEvent, pk=pk)
req = parse_json_request(request)

# cannot launch tally on an election whose voting period is still open
# or has not even started.
Expand Down Expand Up @@ -3016,7 +3008,7 @@ def post(self, request, pk):

# log the action
action = Action(
executer=request.user,
executer=user,
receiver=None,
action_name='authevent:tally',
event=auth_event,
Expand All @@ -3035,6 +3027,24 @@ def post(self, request, pk):
# celery task
return json_response()

def post(self, request, pk):
'''
Launches the tallly in a celery background task. If the
election has children, also launches the tally for them.
'''
# check permissions
permission_required(
request.user,
'AuthEvent',
['edit', 'tally'],
pk
)

# get AuthEvent and parse request json
req = parse_json_request(request)
self.tally_status_post(pk, req, request.user)


def get(self, request, pk):
'''
Returns the tally status of an election and its children
Expand Down

0 comments on commit 98519f6

Please sign in to comment.