From 98519f6e423b0011b3234887076b745520b2b55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Robles?= Date: Wed, 29 Jan 2025 20:04:17 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Automated=20Allow/Launch=20Tally=20?= =?UTF-8?q?(#363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parent issue: https://github.com/sequentech/meta/issues/1003 --- iam/api/models.py | 20 ++++++++++++++++++++ iam/api/tasks.py | 47 +++++++++++++++++++++++++++++++++++++++-------- iam/api/views.py | 30 ++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/iam/api/models.py b/iam/api/models.py index 165f0ef7..823ac524 100644 --- a/iam/api/models.py +++ b/iam/api/models.py @@ -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): @@ -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 @@ -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): diff --git a/iam/api/tasks.py b/iam/api/tasks.py index 517943b5..9da8997b 100644 --- a/iam/api/tasks.py +++ b/iam/api/tasks.py @@ -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__) @@ -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, @@ -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']) + ) diff --git a/iam/api/views.py b/iam/api/views.py index 092f436d..9ec04a6a 100644 --- a/iam/api/views.py +++ b/iam/api/views.py @@ -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. @@ -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, @@ -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