From 78f6de187a5dc16b2f2e76940b21c734e629212a Mon Sep 17 00:00:00 2001 From: Victoria Earl Date: Wed, 15 Jan 2025 19:17:07 -0500 Subject: [PATCH] Add report for deleted Guidebook items We use the tracking table to grab all items that were synced to guidebook but have since been deleted, and send that out in an email. We piggyback on the Guidebook updates email when at-con and check every twelve hours otherwise. Also turns these emails off by default -- to enable them, you need to define the email address they get sent to. --- uber/configspec.ini | 4 ++ uber/tasks/panels.py | 71 +++++++++++++++++++-- uber/templates/emails/guidebook_deletes.txt | 7 ++ uber/templates/emails/guidebook_updates.txt | 11 +++- 4 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 uber/templates/emails/guidebook_deletes.txt diff --git a/uber/configspec.ini b/uber/configspec.ini index 99182f21c..8df2f6409 100644 --- a/uber/configspec.ini +++ b/uber/configspec.ini @@ -737,6 +737,10 @@ panels_confirm_deadline = integer(default=0) # be ready, so we just set this to false whenever it is. hide_schedule = boolean(default=True) +# Emails about changes to schedule items and other Guidebook items are sent TO this address. +# If the address is not set, these emails are disabled. +guidebook_updates_email = string(default="") + # These are the areas from which we'll show events to associated with panel # applications on the schedule. panel_rooms = string_list(default=list()) diff --git a/uber/tasks/panels.py b/uber/tasks/panels.py index 110f21325..1a7ad65cf 100644 --- a/uber/tasks/panels.py +++ b/uber/tasks/panels.py @@ -1,16 +1,47 @@ +import json + +from collections import defaultdict from datetime import timedelta from dateutil import parser as dateparser from sqlalchemy import or_ from uber.config import c from uber.decorators import render -from uber.models import Session +from uber.models import Email, Session, Tracking from uber.tasks import celery from uber.tasks.email import send_email from uber.utils import GuidebookUtils, localized_now -__all__ = ['panels_waitlist_unaccepted_panels', 'sync_guidebook_models', 'check_stale_guidebook_models'] +__all__ = ['panels_waitlist_unaccepted_panels', 'sync_guidebook_models', + 'check_deleted_guidebook_models', 'check_stale_guidebook_models'] + + +def _get_deleted_models(session, deleted_since=None): + deleted_synced = session.query(Tracking).filter(Tracking.action == c.DELETED, + Tracking.snapshot.contains('"last_synced": {"data": {"guidebook"')) + if deleted_since: + deleted_synced = deleted_synced.filter(Tracking.when > deleted_since) + + deleted_models = defaultdict(list) + model_names = {} + + for key, label in c.GUIDEBOOK_MODELS: + model_names[key] = label + + for tracking_entry in deleted_synced: + snapshot = json.loads(tracking_entry.snapshot) + + model = snapshot['_model'] + if model == 'GuestGroup': + model += '_band' if snapshot['group_type'] == c.BAND else '_guest' + elif model == 'Group': + model += '_dealer' + + model_name = 'Schedule Item' if model == 'Event' else model_names[model] + + deleted_models[model_name].append(snapshot['last_synced']['data']['guidebook']['name']) + return deleted_models @celery.task @@ -31,9 +62,30 @@ def sync_guidebook_models(selected_model, sync_time, id_list): session.commit() +@celery.schedule(timedelta(hours=12)) +def check_deleted_guidebook_models(): + if not c.PRE_CON or not c.GUIDEBOOK_UPDATES_EMAIL: + return + + with Session() as session: + subject = f"Deleted Guidebook Items: {localized_now().strftime("%A %-I:%M %p")}" + last_email = session.query(Email).filter(Email.subject.contains("Deleted Guidebook Items") + ).order_by(Email.when.desc()).first() + + deleted_models = _get_deleted_models(session, deleted_since=last_email.when) if last_email else _get_deleted_models(session) + + if deleted_models: + body = render('emails/guidebook_deletes.txt', { + 'deleted_models': deleted_models, + }, encoding=None) + send_email.delay(c.REPORTS_EMAIL, c.GUIDEBOOK_UPDATES_EMAIL, + subject, body, ident="guidebook_deletes" + ) + + @celery.schedule(timedelta(minutes=15)) def check_stale_guidebook_models(): - if not c.AT_THE_CON: + if not c.AT_THE_CON or not c.GUIDEBOOK_UPDATES_EMAIL: return with Session() as session: @@ -41,11 +93,20 @@ def check_stale_guidebook_models(): stale_models = [key for key in cl_updates if cl_updates[key]] if schedule_updates: stale_models.append('Schedule') - if stale_models: + + last_email = session.query(Email).filter(or_( + Email.subject.contains("Guidebook Updates"), + Email.subject.contains("Deleted Guidebook Items")) + ).order_by(Email.when.desc()).first() + + deleted_models = _get_deleted_models(session, deleted_since=last_email.when) if last_email else _get_deleted_models(session) + + if stale_models or deleted_models: body = render('emails/guidebook_updates.txt', { 'stale_models': stale_models, + 'deleted_models': deleted_models, }, encoding=None) - send_email.delay(c.REPORTS_EMAIL, "gb-ops@magfest.org", + send_email.delay(c.REPORTS_EMAIL, c.GUIDEBOOK_UPDATES_EMAIL, f"Guidebook Updates: {localized_now().strftime("%A %-I:%M %p")}", body, ident="guidebook_updates" ) diff --git a/uber/templates/emails/guidebook_deletes.txt b/uber/templates/emails/guidebook_deletes.txt new file mode 100644 index 000000000..d0ef08342 --- /dev/null +++ b/uber/templates/emails/guidebook_deletes.txt @@ -0,0 +1,7 @@ +Attention Guidebook admins! The items below have been deleted from the system. + +{% for category in deleted_models %}{{ category }}(s): +{% for item in deleted_models[category] %} - {{ item }} +{% endfor %}{% endfor %} + +This email will be the only record of these item deletions. Please keep it until you delete all the above items from Guidebook. \ No newline at end of file diff --git a/uber/templates/emails/guidebook_updates.txt b/uber/templates/emails/guidebook_updates.txt index 66df1154b..364e2adde 100644 --- a/uber/templates/emails/guidebook_updates.txt +++ b/uber/templates/emails/guidebook_updates.txt @@ -1,6 +1,13 @@ Attention Guidebook admins! -The following categories have stale items: + +{% if stale_models %}The following categories have been updated in the system: {% for label in stale_models %}- {{ label }} {% endfor %} -You can review all pending items here: {{ c.URL_BASE }}/schedule_reports/index \ No newline at end of file +{% endif %}{% if deleted_models %}The following items have been deleted: +{% for category in deleted_models %}{{ category }}(s): +{% for item in deleted_models[category] %} - {{ item }} +{% endfor %} +{% endfor %} + +{% endif %}You can review all pending items here: {{ c.URL_BASE }}/schedule_reports/index \ No newline at end of file