views.py 12.1 KB
Newer Older
1 2
import logging
import os
Jonah Kanner's avatar
Jonah Kanner committed
3
from lal import gpstime
4 5

from django.urls import reverse
6
from django.views.generic.detail import DetailView
Jonah Kanner's avatar
Jonah Kanner committed
7
from django.views.generic import ListView
8

9 10 11
from guardian.shortcuts import get_objects_for_user

from core.file_utils import get_file_list
12 13 14
from events.models import EMGroup
from events.mixins import DisplayFarMixin
from events.permission_utils import is_external
15
from .mixins import ExposeHideMixin, OperatorSignoffMixin, \
16
    AdvocateSignoffMixin, PermissionsFilterMixin, ConfirmGwFormMixin
17
from .models import Superevent, VOEvent
18
from .utils import get_superevent_by_date_id_or_404
19 20


21
# Set up logger
22 23 24
logger = logging.getLogger(__name__)


25
class SupereventDetailView(OperatorSignoffMixin, AdvocateSignoffMixin,
26 27
    ExposeHideMixin, ConfirmGwFormMixin, DisplayFarMixin,
    PermissionsFilterMixin, DetailView):
28 29 30
    """
    Detail view for superevents.
    """
31 32
    model = Superevent
    template_name = 'superevents/detail.html'
33
    filter_permissions = ['superevents.view_superevent']
34 35

    def get_queryset(self):
36
        """Get queryset and preload some related objects"""
37 38 39 40 41 42 43 44 45 46 47 48 49
        qs = super(SupereventDetailView, self).get_queryset()

        # Do some optimization
        qs = qs.select_related('preferred_event__group',
            'preferred_event__pipeline', 'preferred_event__search')
        qs = qs.prefetch_related('labelling_set', 'events')

        return qs

    def get_object(self, queryset=None):
        if queryset is None:
            queryset = self.get_queryset()
        superevent_id = self.kwargs.get('superevent_id')
50
        obj = get_superevent_by_date_id_or_404(superevent_id, queryset)
51 52 53 54 55 56 57 58 59 60 61 62
        return obj

    def get_context_data(self, **kwargs):
        # Get base context
        context = super(SupereventDetailView, self).get_context_data(**kwargs)

        # Add a bunch of extra stuff
        superevent = self.object
        context['preferred_event'] = superevent.preferred_event
        context['preferred_event_labelling'] = superevent.preferred_event \
            .labelling_set.prefetch_related('label', 'creator').all()

63 64
        # TODO: filter events for user? Not clear what information we want
        # to show to different groups
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
        # Pass event graceids
        context['internal_events'] = superevent.get_internal_events() \
            .order_by('id')
        context['external_events'] = superevent.get_external_events() \
            .order_by('id')

        # Get display FARs for preferred_event
        context.update(zip(
            ['display_far', 'display_far_hr', 'far_is_upper_limit'],
            self.get_display_far(obj=superevent.preferred_event)
            )
        )

        # Is the user an external user? (I.e., not part of the LVC?) The
        # template needs to know that in order to decide what pieces of
        # information to show.
        context['user_is_external'] = is_external(self.request.user)

83
        # Get list of EMGroup names for emo creation form
84 85 86 87 88 89
        context['emgroups'] = EMGroup.objects.all().order_by('name') \
            .values_list('name', flat=True)

        return context


90 91 92 93 94 95 96 97 98
class SupereventFileList(SupereventDetailView):
    """
    List of files associated with a superevent.
    """
    model = Superevent
    template_name = 'superevents/file_list.html'
    filter_permissions = ['superevents.view_superevent']
    log_view_permission = 'superevents.view_log'
    sort_files = True
99

100 101 102 103
    def get_context_data(self, **kwargs):
        # We actually don't want the context from the SupereventDetailView or
        # its mixins so we just override it with the base DetailView
        context = DetailView.get_context_data(self, **kwargs)
Tanner Prestegard's avatar
Tanner Prestegard committed
104

105 106 107
        # Get list of logs which are viewable by the user
        viewable_logs = get_objects_for_user(self.request.user, 
            self.log_view_permission, self.object.log_set.all())
108

109 110 111 112
        # Here we get the list of files
        file_list = get_file_list(viewable_logs, self.object.datadir)
        if self.sort_files:
            file_list = sorted(file_list)
113

114 115
        # Compile the new context data
        context['file_list'] = file_list
116

117
        return context
Jonah Kanner's avatar
Jonah Kanner committed
118

119 120 121
# NOTE: file "detail" or downloads (and associated permissions) are
# handled through the API. Links on the file list page point to the
# API file download page.
Jonah Kanner's avatar
Jonah Kanner committed
122 123 124 125

class SupereventPublic(ListView):
    model = Superevent
    template_name = 'superevents/public.html'
Jonah Kanner's avatar
Jonah Kanner committed
126 127 128
    filter_permissions = ['superevents.view_superevent']
    log_view_permission = 'superevents.view_log'    

Jonah Kanner's avatar
Jonah Kanner committed
129 130
    def get_queryset(self, **kwargs):
        # -- Query only for public events
Jonah Kanner's avatar
Jonah Kanner committed
131
        qs = Superevent.objects.filter(is_exposed=True, category='P')  #-- Change cateogry to P for production
Jonah Kanner's avatar
Jonah Kanner committed
132 133
        return qs
        
Jonah Kanner's avatar
Jonah Kanner committed
134 135 136 137
    def get_context_data(self, **kwargs):
        # Get base context
        context = ListView.get_context_data(self, **kwargs)

Jonah Kanner's avatar
Jonah Kanner committed
138 139
        #-- For each superevent, get list of log messages and construct pastro string
        candidates = 0
Jonah Kanner's avatar
Jonah Kanner committed
140 141
        for se in context['object_list']:

Jonah Kanner's avatar
Jonah Kanner committed
142
            se.maplocal = "/apiweb/superevents/{0}/files/bayestar.png".format(se.superevent_id)
Jonah Kanner's avatar
Jonah Kanner committed
143

Jonah Kanner's avatar
Jonah Kanner committed
144 145 146 147
            #-- GCN links
            se.noticeurl = "https://gcn.gsfc.nasa.gov/notices_l/{0}.lvc".format(se.default_superevent_id)
            se.gcnurl    = "https://gcn.gsfc.nasa.gov/other/GW{0}.gcn3".format(se.default_superevent_id[1:])
            
Jonah Kanner's avatar
Jonah Kanner committed
148 149
            se.ifar_yrs = 1.0 / (se.far*3600*24*365.0)
            se.t0_iso = gpstime.gps_to_utc(se.t_0).isoformat(' ').split('.')[0]
Jonah Kanner's avatar
Jonah Kanner committed
150
            se.t0_utc = se.t0_iso.split()[1]
Jonah Kanner's avatar
Jonah Kanner committed
151 152 153 154 155 156 157 158 159 160 161 162 163 164
            
            #-- Get list of voevents
            voevents = se.voevent_set.all()

            # -- Filter out retractions
            good_voevents = sorted([voe for voe in voevents if voe.voevent_type != 'RE'],key=lambda voe:voe.N)
            
            # -- Find retractions
            retraction_list = [voe for voe in voevents if voe.voevent_type == 'RE']
            if len(retraction_list) > 0:
                se.retract = True
            else:
                se.retract = False
                candidates += 1
Jonah Kanner's avatar
Jonah Kanner committed
165 166 167 168 169 170 171 172


            viewable_logs = get_objects_for_user(self.request.user,
                                                 self.log_view_permission,
                                                 se.log_set.all()).filter(tags__name='analyst_comments') #-- change to analyst_comments
            

            se.comments = ' ** '.join([log.comment for log in viewable_logs]) 
Jonah Kanner's avatar
Jonah Kanner committed
173
            if se.retract: se.comments += "-RETRACTED-"
Jonah Kanner's avatar
Jonah Kanner committed
174
            
Jonah Kanner's avatar
Jonah Kanner committed
175 176 177 178 179 180
            # -- Read out probabilities
            voe = good_voevents[-1]
            pastro_values = [ ("BNS",voe.prob_bns), ("NSBH",voe.prob_nsbh),
                              ("BBH", voe.prob_bbh), ("Terrestrial", voe.prob_terrestrial),
                              ("MassGap", voe.prob_mass_gap) ]

181
            pastro_values.sort(reverse=True, key=lambda (a,b):b)
Jonah Kanner's avatar
Jonah Kanner committed
182 183 184 185 186 187 188 189 190 191
            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

Jonah Kanner's avatar
Jonah Kanner committed
192
            
Jonah Kanner's avatar
Jonah Kanner committed
193
        context['candidates']=candidates
194 195 196 197

        #-- Is this user outside the LVC?
        context['user_is_external'] = is_external(self.request.user)
        
Jonah Kanner's avatar
Jonah Kanner committed
198 199 200
        return context


Jonah Kanner's avatar
Jonah Kanner committed
201
class SupereventPublic2(DisplayFarMixin, ListView):
202 203 204 205 206 207 208 209
    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'

Jonah Kanner's avatar
Jonah Kanner committed
210
    
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
    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:])
Jonah Kanner's avatar
Jonah Kanner committed
243
            
244 245 246
            se.t0_iso = gpstime.gps_to_utc(se.t_0).isoformat(' ').split('.')[0]
            se.t0_utc = se.t0_iso.split()[1]

Jonah Kanner's avatar
Jonah Kanner committed
247
            # Get display FARs for preferred_event
Jonah Kanner's avatar
Jonah Kanner committed
248
            se.far_hz, se.far_hr, se.far_limit = self.get_display_far(obj=se.preferred_event)
Jonah Kanner's avatar
Jonah Kanner committed
249 250 251
            
            

252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
            #-- 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)))
Jonah Kanner's avatar
Jonah Kanner committed
274
            if se.retract: se.comments += "RETRACTED"
275

Jonah Kanner's avatar
Jonah Kanner committed
276 277 278 279 280 281 282 283
            # -- Get list of PE results
            pe_results = get_objects_for_user(self.request.user,
                self.log_view_permission,
                klass=se.log_set.filter(tags__name='pe_result'))
            # Compile comments from these logs
            se.pe = ' ** '.join(list(pe_results.values_list(
                'comment', flat=True)))
            
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
            # -- 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