From 15997c8c5da2ba422233f1960f68b325bc4e1f5c Mon Sep 17 00:00:00 2001
From: Tanner Prestegard <tanner.prestegard@ligo.org>
Date: Thu, 9 May 2019 14:06:50 -0500
Subject: [PATCH] Minor search backend enhancements

---
 gracedb/search/query/events.py      |  8 +++---
 gracedb/search/query/labels.py      |  6 ++--
 gracedb/search/query/superevents.py | 44 ++++++++++++++++++++---------
 3 files changed, 37 insertions(+), 21 deletions(-)

diff --git a/gracedb/search/query/events.py b/gracedb/search/query/events.py
index 0d0d51b57..10c5e5b50 100644
--- a/gracedb/search/query/events.py
+++ b/gracedb/search/query/events.py
@@ -22,7 +22,7 @@ from django.db.models.query import QuerySet
 # (weak) natural language time parsing.
 from events.nltime import nlTimeExpression as nltime_
 nltime = nltime_.setParseAction(lambda toks: toks["calculatedTime"])
-from events.models import Group, Pipeline, Search, Label
+from events.models import Group, Pipeline, Search
 from .labels import getLabelQ
 from .superevents import parse_superevent_id, superevent_expr
 from ..constants import RUN_MAP, ExpressionOperator
@@ -252,7 +252,7 @@ def parseQuery(s):
     # Analysis Groups
     # XXX Querying the database at module compile time is a bad idea!
     # See: https://docs.djangoproject.com/en/1.8/topics/testing/overview/
-    groupNames = [group.name for group in Group.objects.all()]
+    groupNames = list(Group.objects.values_list('name', flat=True))
     group = Or(map(CaselessLiteral, groupNames)).setName("analysis group name")
     #groupList = delimitedList(group, delim='|').setName("analysis group list")
     groupList = OneOrMore(group).setName("analysis group list")
@@ -261,7 +261,7 @@ def parseQuery(s):
         Q(group__name__in=toks.asList())))
 
     # Pipeline
-    pipelineNames = [pipeline.name for pipeline in Pipeline.objects.all()]
+    pipelineNames = list(Pipeline.objects.values_list('name', flat=True))
     pipeline = Or(map(CaselessLiteral, pipelineNames)).setName("pipeline name")
     pipelineList = OneOrMore(pipeline).setName("pipeline list")
     pipelineQ = (Optional(Suppress(Keyword("pipeline:"))) + pipelineList)
@@ -269,7 +269,7 @@ def parseQuery(s):
         Q(pipeline__name__in=toks.asList())))
 
     # Search
-    searchNames = [search.name for search in Search.objects.all()]
+    searchNames = list(Search.objects.values_list('name', flat=True))
     search = Or(map(CaselessLiteral, searchNames)).setName("search name")
     # XXX Branson: The change below was made 2/17/15 to fix a bug in which 
     # searches like 'grbevent.ra > 0' failed due to the 'grb' being peeled off
diff --git a/gracedb/search/query/labels.py b/gracedb/search/query/labels.py
index f6e885da1..8bffac073 100644
--- a/gracedb/search/query/labels.py
+++ b/gracedb/search/query/labels.py
@@ -18,7 +18,7 @@ def getLabelQ():
     # Note the parse action for labelQ: Replace all tokens with the empty
     # string. This basically has the effect of removing any label query terms
     # from the query string.
-    labelNames = [l.name for l in Label.objects.all()]
+    labelNames = list(Label.objects.values_list('name', flat=True))
     #label = Or([CaselessLiteral(n) for n in labelNames]).\
     label = Or([CaselessKeyword(n) for n in labelNames]).\
             setParseAction( lambda toks: Q(labels__name=toks[0]) )
@@ -39,7 +39,7 @@ def getLabelQ():
 # as a list of Q objects and separators.
 #--------------------------------------------------------------------------
 def labelQuery(s, names=False):
-    labelNames = [l.name for l in Label.objects.all()]
+    labelNames = list(Label.objects.values_list('name', flat=True))
     #label = Or([CaselessLiteral(n) for n in labelNames])
     label = Or([CaselessKeyword(n) for n in labelNames])
     # If the filter objects are going to be applied to Lable
@@ -66,7 +66,7 @@ def labelQuery(s, names=False):
 # The following version is used only for validation. Just to check that
 # the query strictly conforms to the requirements of a label query.
 def parseLabelQuery(s):
-    labelNames = [l.name for l in Label.objects.all()]
+    labelNames = list(Label.objects.values_list('name', flat=True))
     #label = Or([CaselessLiteral(n) for n in labelNames])
     label = Or([CaselessKeyword(n) for n in labelNames])
     andop   = oneOf(", &")
diff --git a/gracedb/search/query/superevents.py b/gracedb/search/query/superevents.py
index 418b2f790..a0194fa8e 100644
--- a/gracedb/search/query/superevents.py
+++ b/gracedb/search/query/superevents.py
@@ -3,11 +3,11 @@ import datetime
 import logging
 import pytz
 
+from django.conf import settings
 from django.db.models import Q
 from django.db.models.query import QuerySet
 
 from core.utils import letters_to_int, int_to_letters
-from events.models import Group, Pipeline, Search, Label
 # (weak) natural language time parsing.
 from events.nltime import nlTimeExpression as nltime_
 from superevents.models import Superevent
@@ -19,7 +19,7 @@ from pyparsing import Word, nums, Literal, CaselessLiteral, delimitedList, \
     Suppress, QuotedString, Keyword, Combine, Or, Optional, OneOrMore, \
     ZeroOrMore, alphas, alphanums, Regex, opAssoc, operatorPrecedence, \
     oneOf, stringStart,  stringEnd, FollowedBy, ParseResults, ParseException, \
-    CaselessKeyword, pyparsing_common
+    CaselessKeyword, pyparsing_common, tokenMap
 
 # Set up logger
 logger = logging.getLogger(__name__)
@@ -37,9 +37,23 @@ def parse_superevent_id(name, toks, filter_prefix=None):
         if (toks.prefix == Superevent.GW_ID_PREFIX):
             toks.suffix = toks.suffix.upper()
 
-    # Combine into full ID, get lookup kwargs and convert to a Q object
+    # Combine into full ID and get lookup kwargs
     s_id = toks.preprefix + toks.prefix + toks.date + toks.suffix
     f_kwargs = Superevent.get_filter_kwargs_for_date_id_lookup(s_id)
+
+    # Add any necessary prefix. For example, if we want to look up events
+    # that are part of a given superevent, we have to add the 'superevent__'
+    # prefix to the filter kwargs.
+    if filter_prefix:
+
+        # Add '__' to end of filter_prefix
+        if not filter_prefix.endswith('__'):
+            filter_prefix += '__'
+
+        f_kwargs = {'{pref}{k}'.format(pref=filter_prefix, k=k): v for
+            k,v in f_kwargs.items()}
+
+    # Convert to a Q object
     fullQ = Q(**f_kwargs)
 
     return (name, fullQ)
@@ -80,7 +94,7 @@ parameter_dicts = {
     't_0': {
         'keyword': ['t_0', 'gpstime'],
         'keywordOptional': True,
-        'value': pyparsing_common.number,
+        'value': pyparsing_common.number.copy(),
         'doRange': True,
         'parseAction': maybeRange('t_0'),
     },
@@ -88,7 +102,7 @@ parameter_dicts = {
     't_start': {
         'keyword': 't_start',
         'keywordOptional': False,
-        'value': pyparsing_common.number,
+        'value': pyparsing_common.number.copy(),
         'doRange': True,
         'parseAction': maybeRange('t_start'),
     },
@@ -96,7 +110,7 @@ parameter_dicts = {
     't_end': {
         'keyword': 't_end',
         'keywordOptional': False,
-        'value': pyparsing_common.number,
+        'value': pyparsing_common.number.copy(),
         'doRange': True,
         'parseAction': maybeRange('t_end'),
     },
@@ -113,16 +127,18 @@ parameter_dicts = {
     'preferred_event': {
         'keyword': 'preferred_event',
         'keywordOptional': False,
-        'value': Suppress(Word("GHMTghmt", exact=1)) + Word(nums),
+        'value': Suppress(Word("GHMTghmt", exact=1)) + \
+            Word(nums).setParseAction(tokenMap(int)),
         'doRange': True,
         'parseAction': maybeRange("preferred_event",
             dbname="preferred_event__id"),
     },
     # event: G1234, event: G1234 .. G2234
     'event': {
-        'keyword': 'event',
+        'keyword': ['event', 'events'],
         'keywordOptional': False,
-        'value': Suppress(Word("EGHMTeghmt", exact=1)) + Word(nums),
+        'value': Suppress(Word("EGHMTeghmt", exact=1)) + \
+            Word(nums).setParseAction(tokenMap(int)),
         'doRange': True,
         'parseAction': maybeRange("event", dbname="events__id"),
     },
@@ -167,7 +183,8 @@ parameter_dicts = {
             Optional(Word(nums, exact=2) + Suppress(':') + \
             Word(nums, exact=2) + Optional(Suppress(':') + \
             Word(nums, exact=2)))).setParseAction(lambda toks:
-            pytz.utc.localize(datetime.datetime(*map(int, toks)))),
+            pytz.timezone(settings.TIME_ZONE).localize(
+            datetime.datetime(*map(int, toks)))),
         'parseAction': maybeRange("created"),
     },
     # test OR category: test
@@ -185,7 +202,7 @@ parameter_dicts = {
     'far': {
         'doRange': False,
         'value': Suppress(CaselessLiteral('far')) + ExpressionOperator + \
-            pyparsing_common.number,
+            pyparsing_common.number.copy(),
         'parseAction': lambda toks: ("far",
             Q(**{'preferred_event__far' + toks[0]: toks[1]})),
     },
@@ -194,9 +211,9 @@ parameter_dicts = {
         'doRange': False,
         'value': Suppress(CaselessLiteral('far')) + \
             Suppress(Or([CaselessLiteral(d) for d in ['in', ':']])) + \
-            pyparsing_common.number + \
+            pyparsing_common.number.copy() + \
             Suppress(Or([CaselessLiteral(op) for op in [',', '..', '-']])) + \
-            pyparsing_common.number,
+            pyparsing_common.number.copy(),
         'parseAction': lambda toks: ("far_range",
             Q(**{'preferred_event__far__range': toks.asList()})),
     },
@@ -272,4 +289,3 @@ def parseSupereventQuery(s):
         matches.append(('category', default_Q))
 
     return reduce(Q.__and__, [m[1] for m in matches], Q())
-
-- 
GitLab