From 572926c607f251e6cf2daf89e0cb1b65a9385222 Mon Sep 17 00:00:00 2001
From: Tanner Prestegard <tanner.prestegard@ligo.org>
Date: Thu, 30 May 2019 09:56:33 -0500
Subject: [PATCH] Add customized SupereventPublic2 view

Some improvements and cleanup to SupereventPublic
---
 gracedb/superevents/urls.py  |   5 +-
 gracedb/superevents/views.py | 103 ++++++++++++++++++++++++++++++++++-
 2 files changed, 105 insertions(+), 3 deletions(-)

diff --git a/gracedb/superevents/urls.py b/gracedb/superevents/urls.py
index b72c9c890..ce4860fc2 100644
--- a/gracedb/superevents/urls.py
+++ b/gracedb/superevents/urls.py
@@ -35,6 +35,9 @@ urlpatterns = legacy_urlpatterns + [
         include(suburlpatterns)),
 
     # table of all public events
-    url(r'^public/$', views.SupereventPublic.as_view(), name="pubic-alerts"),
+    url(r'^public/$', views.SupereventPublic.as_view(), name="public-alerts"),
+
+    # Tanner customizations
+    url(r'^public2/$', views.SupereventPublic2.as_view(), name="public-alerts2"),
     
 ]
diff --git a/gracedb/superevents/views.py b/gracedb/superevents/views.py
index 324a9d50c..6f665141f 100644
--- a/gracedb/superevents/views.py
+++ b/gracedb/superevents/views.py
@@ -1,6 +1,8 @@
 import logging
 import os
 from lal import gpstime
+
+from django.urls import reverse
 from django.views.generic.detail import DetailView
 from django.views.generic import ListView
 
@@ -12,7 +14,7 @@ from events.mixins import DisplayFarMixin
 from events.permission_utils import is_external
 from .mixins import ExposeHideMixin, OperatorSignoffMixin, \
     AdvocateSignoffMixin, PermissionsFilterMixin, ConfirmGwFormMixin
-from .models import Superevent
+from .models import Superevent, VOEvent
 from .utils import get_superevent_by_date_id_or_404
 
 
@@ -196,4 +198,101 @@ class SupereventPublic(ListView):
         return context
 
 
-    
+class SupereventPublic2(ListView):
+    model = Superevent
+    template_name = 'superevents/public.html'
+    filter_permissions = ['superevents.view_superevent']
+    log_view_permission = 'superevents.view_log'
+    noticeurl_template = 'https://gcn.gsfc.nasa.gov/notices_l/{s_id}.lvc'
+    gcnurl_template = 'https://gcn.gsfc.nasa.gov/other/GW{sd_id}.gcn3'
+    skymap_filename = 'bayestar.png'
+
+    def get_queryset(self, **kwargs):
+        # -- Query only for public events
+        # Comment from Tanner: Use the category directly from the superevent
+        # model rather than hard-coding
+        qs = Superevent.objects.filter(is_exposed=True,
+            category=Superevent.SUPEREVENT_CATEGORY_PRODUCTION) \
+            .prefetch_related('voevent_set', 'log_set')
+        return qs
+
+    def get_context_data(self, **kwargs):
+        # Get base context
+        # Comment from Tanner: use super() here; it's equivalent in this case
+        # to directly calling ListView.get_context_data, but super() is
+        # typically better practice
+        context = super(SupereventPublic2, self).get_context_data(**kwargs)
+
+        #-- For each superevent, get list of log messages and construct pastro string
+        candidates = 0
+        for se in self.object_list:
+
+            # Comment from Tanner: use reverse() for internal URLs
+            se.maplocal = reverse('legacy_apiweb:default:superevents:superevent-file-detail',
+                args=[se.default_superevent_id, self.skymap_filename])
+
+            #-- GCN links
+            # Comment from Tanner: define link templates outside of function
+            # so they are loaded at "compile-time" rather than each time the
+            # function is called
+            se.noticeurl = self.noticeurl_template.format(s_id=
+                se.default_superevent_id)
+            se.gcnurl = self.gcnurl_template.format(sd_id=
+                se.default_superevent_id[1:])
+
+            # Comment from Tanner: suggest to refactor ifar yrs so it shows as
+            # either 1 per X yrs or 1/X per 1 yr, depending on which one
+            # is most understandable (see lines 389-398 of events/views.py)
+            # However: is this right? Looks like we are presenting iFAR as FAR
+            # in the template.  Maybe I'm not understanding it, though.
+            se.ifar_yrs = 1.0 / (se.far*3600*24*365.0)
+            se.t0_iso = gpstime.gps_to_utc(se.t_0).isoformat(' ').split('.')[0]
+            se.t0_utc = se.t0_iso.split()[1]
+
+            #-- Get list of voevents
+            # Comment from Tanner: do as much work as you can in the database.
+            # Also, use VOEvent types from the model rather than hard-coding
+            # -- Filter out retractions
+            # Comment from Tanner: looks like we only ever use the
+            # non-retraction VOEvent with the highest N, so let's just get
+            # it in one query.
+            voe = se.voevent_set.exclude(voevent_type=
+                VOEvent.VOEVENT_TYPE_RETRACTION).order_by('-N').first()
+            # -- Was this candidate retracted?
+            se.retract = se.voevent_set.filter(voevent_type=
+                VOEvent.VOEVENT_TYPE_RETRACTION).exists()
+            candidates += int(not se.retract)
+
+            # Get list of viewable logs for user which are tagged with
+            # 'analyst_comments'
+            viewable_logs = get_objects_for_user(self.request.user,
+                self.log_view_permission,
+                klass=se.log_set.filter(tags__name='analyst_comments'))
+            # Compile comments from these logs
+            se.comments = ' ** '.join(list(viewable_logs.values_list(
+                'comment', flat=True)))
+            if se.retract: se.comments += " ** RETRACTED ** "
+
+            # -- Read out probabilities
+            pastro_values = [ ("BNS",voe.prob_bns), ("NSBH",voe.prob_nsbh),
+                              ("BBH", voe.prob_bbh), ("Terrestrial", voe.prob_terrestrial),
+                              ("MassGap", voe.prob_mass_gap) ]
+
+            pastro_values.sort(reverse=True, key=lambda (a,b):b)
+            sourcelist = []
+            for key, value in pastro_values:
+                if value > 0.01:
+                    prob = int(round(100*value))
+                    if prob == 100: prob = '>99'
+                    sourcestr = "{0} ({1}%)".format(key, prob)
+                    sourcelist.append(sourcestr)
+            se.sourcetypes = ', '.join(sourcelist)
+            se.N = voe.N
+
+        # Number of non-retracted candidate events
+        context['candidates'] = candidates
+
+        #-- Is this user outside the LVC?
+        context['user_is_external'] = is_external(self.request.user)
+
+        return context
-- 
GitLab