This is an automated email from the ASF dual-hosted git repository. gstein pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/steve.git
commit 1af3a006f9c197f294c420cac94163fb2cc96986 Author: Greg Stein <[email protected]> AuthorDate: Thu Oct 2 03:31:56 2025 -0500 Format and display more Election data. * pages.py: format dates. better test/random data. * voter.ezt: present cards with all Election data. Draft. --- v3/server/pages.py | 70 +++++++++++++++++++++++++++++++++++++++---- v3/server/templates/voter.ezt | 44 ++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 10 deletions(-) diff --git a/v3/server/pages.py b/v3/server/pages.py index c394e0e..4938350 100644 --- a/v3/server/pages.py +++ b/v3/server/pages.py @@ -24,12 +24,14 @@ import sys import pathlib +import datetime from easydict import EasyDict as edict import asfpy.stopwatch import quart import asfquart.session from asfquart.auth import Requirements as R +import ezt APP = asfquart.APP @@ -40,6 +42,12 @@ sys.path.insert(0, str(THIS_DIR.parent)) import steve.election import steve.crypto +# Formatted values to inject into templates. +FMT_DATE = '%b %d' +FMT_DATE_FULL = '%Y-%m-%d %H:%M' +SOON_1HOUR = 60 * 60 +SOON_CUTOFF = 48 * SOON_1HOUR # 48 hours, in seconds + async def signin_info(): "Return EZT template data for the Sign-In, in the upper right." @@ -70,6 +78,23 @@ async def voter_page(): owned = steve.election.Election.owned_elections(DB_FNAME, 'gstein') ### for now + def some_future(): + import random + + # 50% no time + if random.randrange(2): + return None + # 66% days, then: 50% hours or minutes each + if random.randrange(3): + delta = random.randint(10, 50) * 24 * 60 * 60 # days + elif random.randrange(2): + delta = random.randint(5, 40) * 60 * 60 # hours + else: + delta = random.randint(10, 50) * 60 # minutes + + return (datetime.datetime.now() + datetime.timedelta(seconds=delta) + ).timestamp() + def new_test_election(): return edict( eid=steve.crypto.create_id(), @@ -77,17 +102,17 @@ async def voter_page(): owner_pid='alice', authz=None, closed=None, - open_at=None, - close_at=None, + open_at=some_future(), + close_at=some_future(), ) - election = [ new_test_election() ] - owned = [ new_test_election() ] + election = [ new_test_election() for i in range(10) ] + owned = [ new_test_election() for i in range(10) ] result = await signin_info() result.title = 'Voting' - result.election = election - result.owned = owned + result.election = [ postprocess_election(e) for e in election ] + result.owned = [ postprocess_election(e) for e in owned ] return result @@ -148,3 +173,36 @@ async def about_page(): @APP.route('/static/<path:filename>') async def serve_static(filename): return await quart.send_from_directory('static', filename) + + +def format_datetime(dt): + "Format a datetime as absolute or relative." + + # Carry through an absent datetime. + if not dt: + return None + + delta = dt.timestamp() - datetime.datetime.now().timestamp() + if 0 < delta < SOON_CUTOFF: + if delta < SOON_1HOUR: + return f'about {int(delta / 60)} minutes' + return f'about {int(delta / 60 / 60)} hours' + + return dt.strftime(FMT_DATE) # short format + + +def postprocess_election(e): + "Post-process attributes in an Election, as an EasyDict." + + # Anything but 1 means the Election is Open. + e.closed = ezt.boolean(e.closed == 1) + + # Format dates, if present. + dt_open = e.open_at and datetime.datetime.fromtimestamp(e.open_at) + e.fmt_open_at = format_datetime(dt_open) + e.fmt_open_at_full = dt_open and dt_open.strftime(FMT_DATE_FULL) + dt_close = e.close_at and datetime.datetime.fromtimestamp(e.close_at) + e.fmt_close_at = format_datetime(dt_close) + e.fmt_close_at_full = dt_close and dt_close.strftime(FMT_DATE_FULL) + + return e diff --git a/v3/server/templates/voter.ezt b/v3/server/templates/voter.ezt index 2fadefe..24a4b1e 100644 --- a/v3/server/templates/voter.ezt +++ b/v3/server/templates/voter.ezt @@ -1,14 +1,50 @@ [include "header.ezt"] <div class="container"> <h1>[title]</h1> - <p> + [for election] - [election.eid] [election.title] + <div class="col-md-5 mb-4"> + <a href="#" class="text-decoration-none"> + <div class="card h-100"> + <div class="card-body"> + <h5 class="card-title">[election.title]</h5> + <p class="card-text"> + something about the election? + <br/> + eid: [election.eid] + <br/> + created by: [election.owner_pid] + <br/> + authz: [election.authz] + <br/> + closed: [election.closed] + </p> + </div> + <div class="card-footer text-muted"> + <div> + OPEN: + [election.open_at] + [election.fmt_open_at] + [election.fmt_open_at_full] + </div> + <div> + CLOSE: + [election.close_at] + [election.fmt_close_at] + [election.fmt_close_at_full] + </div> + </div> + </div> + </a> + </div> [end] - </p> + <p> [for owned] - [owned.eid] [owned.title] [owned.authz] [owned.closed] + [owned.eid] [owned.title] [owned.owner_pid] + [owned.authz] [owned.closed] + [owned.open_at] [owned.fmt_open_at] [owned.fmt_open_at_full] + [owned.close_at] [owned.fmt_close_at] [owned.fmt_close_at_full] [end] </p> </div>
