Skip to content
Snippets Groups Projects
Commit e877e58e authored by Branson Craig Stephens's avatar Branson Craig Stephens
Browse files

Allow for more complex label queries in searches

and email notifications. This is done by parsing
the label query separately and applying the filter
in a separate step.
parent 2cd7586e
No related branches found
No related tags found
No related merge requests found
......@@ -11,6 +11,10 @@ import logging
from utils import gpsToUtc
from query import filter_for_labels
from gracedb.models import Event
# These imports can be fragile, so they should be brought in only
# if use of the LVAlert overseer is really intended.
if settings.USE_LVALERT_OVERSEER:
......@@ -62,6 +66,15 @@ def issueAlertForLabel(event, label, doxmpp, serialized_event=None, event_url=No
triggers = label.trigger_set.filter(pipelines=pipeline)
triggers = triggers | label.trigger_set.filter(pipelines=None)
for trigger in triggers:
if len(trigger.label_query) > 0:
# construct a queryset containing only this event
qs = Event.objects.filter(id=event.id)
qs = filter_for_labels(qs, trigger.label_query)
# If the label query cleans out our query set, we'll continue
# without adding the recipient.
if qs.count() == 0:
continue
for recip in trigger.contacts.all():
profileRecips.append(recip.email)
......
......@@ -8,7 +8,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import FieldError
from django.forms import ModelForm
from query import parseQuery
from query import parseQuery, filter_for_labels
from pyparsing import ParseException
htmlEntityStar = "★"
......@@ -24,7 +24,10 @@ class GraceQueryField(forms.CharField):
from django.db.models import Q
queryString = forms.CharField.clean(self, queryString)
try:
return Event.objects.filter(parseQuery(queryString)).distinct()
#return Event.objects.filter(parseQuery(queryString)).distinct()
qs = Event.objects.filter(parseQuery(queryString))
qs = filter_for_labels(qs, queryString)
return qs.distinct()
except ParseException, e:
err = "Error: " + escape(e.pstr[:e.loc]) + errorMarker + escape(e.pstr[e.loc:])
raise forms.ValidationError(mark_safe(err))
......
......@@ -19,12 +19,13 @@ nltime = nltime_.setParseAction(lambda toks: toks["calculatedTime"])
import datetime
import models
from django.db.models import Q
from django.db.models.query import QuerySet
from pyparsing import \
Word, nums, Literal, CaselessLiteral, delimitedList, Suppress, QuotedString, \
Keyword, Combine, Or, Optional, OneOrMore, alphas, alphanums, Regex, \
Keyword, Combine, Or, Optional, OneOrMore, ZeroOrMore, alphas, alphanums, Regex, \
opAssoc, operatorPrecedence, oneOf, \
stringStart, stringEnd, FollowedBy
stringStart, stringEnd, FollowedBy, ParseResults, ParseException
def maybeRange(name, dbname=None):
dbname = dbname or name
......@@ -86,6 +87,9 @@ runQ = runQ.setParseAction(lambda toks: ("gpstime", Q(gpstime__range=runmap[toks
#lambda toks: ("gpstime", Q("gpstime__range": runmap[toks[0]])) )
# 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 models.Group.objects.all()]
group = Or(map(CaselessLiteral, groupNames)).setName("analysis group name")
#groupList = delimitedList(group, delim='|').setName("analysis group list")
......@@ -178,24 +182,37 @@ dtrange = dt + Suppress("..") + dt
createdQ = Optional(Suppress(Keyword("created:"))) + (nltime^nltimeRange^dt^dtrange)
createdQ = createdQ.setParseAction(maybeRange("created"))
# Labels
labelNames = [l.name for l in models.Label.objects.all()]
label = Or([CaselessLiteral(n) for n in labelNames]).\
setParseAction( lambda toks: Q(labels__name=toks[0]) )
andop = oneOf(", &").suppress()
orop = Literal("|").suppress()
minusop = oneOf("- ~").suppress()
labelQ_ = operatorPrecedence(label,
[(minusop, 1, opAssoc.RIGHT, lambda a,b,toks: ~toks[0][0]),
(orop, 2, opAssoc.LEFT, lambda a,b,toks: reduce(Q.__or__, toks[0].asList(), Q())),
(andop, 2, opAssoc.LEFT, lambda a,b,toks: reduce(Q.__and__, toks[0].asList(), Q())),
]).setParseAction(lambda toks: toks[0])
labelQ = (Optional(Suppress(Keyword("label:"))) + labelQ_.copy())
labelQ.setParseAction(lambda toks: ("label", toks[0]))
# NOTE: The label query has been moved inside the parseQuery call to avoid
# database access at compile time (to get the list of label names).
# NOTE ALSO: This is an old attempt by Brian to get a more complex label logic
# search working. It worked for some searches, but not all. That's because,
# the method below creates a composite Q object that is applied to each
# *individiual* Event, label relationship. So if you search for
#
# EM_READY & ADVOK
#
# The search will not work correctly since it will look for an event with
# a label such that the label is named EM_READY and ADVOK. No single label
# will have both names. filter_for_labels below avoids this problem by applying
# each label Q filter and combining the resulting querysets as appropriate.
#
#labelNames = [l.name for l in models.Label.objects.all()]
#label = Or([CaselessLiteral(n) for n in labelNames]).\
# setParseAction( lambda toks: Q(labels__name=toks[0]) )
#
#andop = oneOf(", &").suppress()
#orop = Literal("|").suppress()
#minusop = oneOf("- ~").suppress()
#
#labelQ_ = operatorPrecedence(label,
# [(minusop, 1, opAssoc.RIGHT, lambda a,b,toks: ~toks[0][0]),
# (orop, 2, opAssoc.LEFT, lambda a,b,toks: reduce(Q.__or__, toks[0].asList(), Q())),
# (andop, 2, opAssoc.LEFT, lambda a,b,toks: reduce(Q.__and__, toks[0].asList(), Q())),
# ]).setParseAction(lambda toks: toks[0])
#
#labelQ = (Optional(Suppress(Keyword("label:"))) + labelQ_.copy())
#labelQ.setParseAction(lambda toks: ("label", toks[0]))
###########################
# Query on event attributes
......@@ -249,6 +266,10 @@ rangeTerm.setParseAction(lambda toks: Q(**{toks[0]+"__range": toks[1:]}))
term = simpleTerm | rangeTerm
andop = oneOf(", &").suppress()
orop = Literal("|").suppress()
minusop = oneOf("- ~").suppress()
attrExpressions = operatorPrecedence(term,
[(minusop, 1, opAssoc.RIGHT, lambda a,b,toks: ~toks[0][0]),
(orop, 2, opAssoc.LEFT, lambda a,b,toks: reduce(Q.__or__, toks[0].asList(), Q())),
......@@ -273,17 +294,40 @@ ifoQ = ifoListQ | nifoQ
###########################
#q = (ifoQ | hasfarQ | gidQ | hidQ | tidQ | eidQ | labelQ | atypeQ | groupQ | gpsQ | createdQ | submitterQ | runQ | attributeQ).setName("query term")
q = (ifoQ | hasfarQ | gidQ | hidQ | tidQ | eidQ | midQ | labelQ | searchQ | pipelineQ | groupQ | gpsQ | createdQ | submitterQ | runQ | attributeQ).setName("query term")
#andTheseTags = ["attr"]
andTheseTags = ["nevents"]
#--------------------------------------------------------------------------
# parseQuery now handles all search terms *except* for the labels.
# The labels have to be handled separately, in filter_for_labels.
#--------------------------------------------------------------------------
def parseQuery(s):
# labelQ is defined inside in order to avoid a compile-time database query
# to get the label names.
# Note the parse action for lableQ: 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 models.Label.objects.all()]
label = Or([CaselessLiteral(n) for n in labelNames]).\
setParseAction( lambda toks: Q(labels__name=toks[0]) )
andop = oneOf(", &")
orop = Literal("|")
minusop = oneOf("- ~")
op = Or([andop,orop,minusop])
oplabel = OneOrMore(op) + label
labelQ_ = Optional(minusop) + label + ZeroOrMore(oplabel)
labelQ = (Optional(Suppress(Keyword("label:"))) + labelQ_.copy())
labelQ.setParseAction(lambda toks: '')
# Clean the label-related parts of the query out of the query string.
s = labelQ.transformString(s)
# A parser for the non-label-related remainder of the query string.
q = (ifoQ | hasfarQ | gidQ | hidQ | tidQ | eidQ | midQ | searchQ | pipelineQ | groupQ | gpsQ | createdQ | submitterQ | runQ | attributeQ).setName("query term")
d={}
if not s:
# Empty query return everything not in Test group and not in the MDC group
#return ~Q(group__name="Test")
return ~Q(group__name="Test") & ~Q(search__name="MDC")
for (tag, qval) in (stringStart + OneOrMore(q) + stringEnd).parseString(s).asList():
if tag in andTheseTags:
......@@ -318,3 +362,140 @@ def parseQuery(s):
del d["hid"]
return reduce(Q.__and__, d.values(), Q())
#--------------------------------------------------------------------------
# Given a query string, separate out the label-related part, and return it
# as a list of Q objects and separators.
#--------------------------------------------------------------------------
def labelQuery(s, names=False):
labelNames = [l.name for l in models.Label.objects.all()]
label = Or([CaselessLiteral(n) for n in labelNames])
# If the filter objects are going to be applied to Lable
# objects to retrieve labels by name, names = True.
# This is useful for the label query in userprofile.models.Trigger
if names:
label.setParseAction( lambda toks: Q(name=toks[0]) )
else:
label.setParseAction( lambda toks: Q(labels__name=toks[0]) )
andop = oneOf(", &")
orop = Literal("|")
minusop = oneOf("- ~")
op = Or([andop,orop,minusop])
oplabel = OneOrMore(op) + label
labelQ_ = Optional(minusop) + label + ZeroOrMore(oplabel)
labelQ = (Optional(Suppress(Keyword("label:"))) + labelQ_.copy())
toks = labelQ.searchString(s).asList()
# This list will have either 1 or 0 elements.
if len(toks):
return toks[0]
return toks
# 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 models.Label.objects.all()]
label = Or([CaselessLiteral(n) for n in labelNames])
andop = oneOf(", &")
orop = Literal("|")
minusop = oneOf("- ~")
op = Or([andop,orop,minusop])
oplabel = OneOrMore(op) + label
labelQ_ = Optional(minusop) + label + ZeroOrMore(oplabel)
labelQ = (Optional(Suppress(Keyword("label:"))) + labelQ_.copy())
return labelQ.parseString(s).asList()
#--------------------------------------------------------------------------
# Given a list of the tokens, go through the list until you hit an AND or
# OR operator. Then apply the operator to the two surrounding query sets
# and send back a new list. The list will be shorter by 2 elements, since
# 'QuerySet, op, QuerySet' has been replaced by a single QuerySet.
#--------------------------------------------------------------------------
def handle_binary_ops(toks, op="or"):
# Find the indices of the relevant operators.
if op == "or":
indices = [i for i, x in enumerate(toks) if x is '|']
elif op == "and":
indices = [i for i, x in enumerate(toks) if x == '&' or x==',']
else:
raise ValueError("Unknown operator")
if len(indices) > 0:
# Found the operator we're looking for
updated = True
i = indices[0] # index of the first operator in the list
leftQS = toks[i-1]
rightQS = toks[i+1]
# Check. The list items surrounding our operator need to be QuerySets
if not isinstance(leftQS, QuerySet) or not isinstance(rightQS, QuerySet):
raise ValueError("problem with query. Orphaned operator?")
# Combine the two QuerySets
if op=="or":
outputQ = leftQS | rightQS
elif op=="and":
outputQ = leftQS & rightQS
# Build up the new list of tokens to return.
new_toks = []
for j in range(len(toks)):
if j == i-1:
new_toks.append(outputQ)
elif j==i or j==i+1:
continue
else:
new_toks.append(toks[j])
else:
# No such operator found, return the list of tokens unmodified.
updated = False
new_toks = toks
return new_toks, updated
#--------------------------------------------------------------------------
# Given a queryset and a queryString (which may contain label search terms),
# filter the queryset for those label terms.
#--------------------------------------------------------------------------
def filter_for_labels(qs, queryString):
import logging
if not queryString or len(queryString)==0:
return qs
# Parse the label part of the query string into its individual tokens.
toks = labelQuery(queryString)
if len(toks)==0:
return qs
# Handle the NOTs first.
not_indices = [i for i, x in enumerate(toks) if x == '~' or x=='-']
for i in not_indices:
if not isinstance(toks[i+1], Q):
raise ValueError("NOT operator should preceed a Label name. Bad Query.")
toks[i+1] = ~toks[i+1]
# Now that we've applied the NOTs, remove them from the list
toks = [x for x in toks if x not in ['-','~']]
# Now the list of tokens consists of filter objects and separators.
# So next, we replace the filters with filtered querysets.
toks = [ qs.filter(f) if isinstance(f,Q) else f for f in toks ]
# Handle the ORs. We take the union of all QuerySets separated by
# OR operators.
updated = True
while updated:
toks, updated = handle_binary_ops(toks,"or")
# Handle the ANDs. Same kinda thang.
updated = True
while updated:
toks, updated = handle_binary_ops(toks,"and")
# By this time, the list of tokens should be down to a single QuerySet.
if len(toks)>1:
raise ValueError("The label query didn't reduce properly.")
return toks[0]
from django.test import TestCase
from django.db.models import Q
from gracedb.models import Event, Label, Labelling
from gracedb.models import Group, Pipeline
from django.contrib.auth.models import User
from gracedb.query import parseQuery, filter_for_labels
QUERY_CASES = {
'all_ors' : { 'query': 'A_LABEL | B_LABEL | C_LABEL', 'pk_list': [2,3,4,5,6,7,8] },
'all_ands' : { 'query': 'A_LABEL & B_LABEL & C_LABEL', 'pk_list': [8] },
'not_a' : { 'query': '~A_LABEL', 'pk_list': [1,3,4,7] },
'a_and_b' : { 'query': 'A_LABEL & B_LABEL', 'pk_list': [5,8] },
'a_or_b' : { 'query': 'A_LABEL | B_LABEL', 'pk_list': [ 2,3,5,6,7,8] },
'a_or_not_b' : { 'query': 'A_LABEL | ~B_LABEL', 'pk_list': [1,2,4,5,6,8] },
'a_and_not_b' : { 'query': 'A_LABEL & ~B_LABEL', 'pk_list': [2,6] },
'one_or_more_missing' : { 'query': '~A_LABEL | ~B_LABEL | ~C_LABEL', 'pk_list': [1,2,3,4,5,6,7] },
}
def get_pks_for_query(queryString):
qs = Event.objects.filter(parseQuery(queryString))
qs = filter_for_labels(qs, queryString)
qs = qs.distinct()
return [int(obj.id) for obj in qs]
class LabelSearchTestCase(TestCase):
def setUp(self):
a = Label.objects.create(name='A_LABEL')
b = Label.objects.create(name='B_LABEL')
c = Label.objects.create(name='C_LABEL')
# Primary keys start with 1. 1 to 8 here.
label_lists = [
[],
[a],
[b],
[c],
[a, b],
[a, c],
[b, c],
[a, b, c],
]
# The required fields on event are: submitter, group, pipeline
submitter = User.objects.create(username='albert.einstein@LIGO.ORG')
group = Group.objects.create(name='Test')
pipeline = Pipeline.objects.create(name='TestPipeline')
for label_list in label_lists:
e = Event.objects.create(submitter=submitter, group=group, pipeline=pipeline)
for label in label_list:
Labelling.objects.create(event=e, label=label, creator=submitter)
def test_all_queries(self):
for key, d in QUERY_CASES.iteritems():
print "Checking %s ... " % key
# Explicitly search for test events
query = 'Test ' + d['query']
self.assertEqual(set(get_pks_for_query(query)), set(d['pk_list']))
def test_bad_query(self):
f = Q(labels__name='A_LABEL') | ~Q(labels__name='B_LABEL')
qs = Event.objects.filter(f)
results = [int(obj.id) for obj in qs]
self.assertEqual(set(results), set([1,2,4,5,6,7,8]))
def test_other_query(self):
f1 = Q(labels__name='A_LABEL')
f2 = ~Q(labels__name='B_LABEL')
qs = Event.objects.filter(f1) | Event.objects.filter(f2)
results = [int(obj.id) for obj in qs]
self.assertEqual(set(results), set([1,2,4,5,6,8]))
def test_intersect_query(self):
f1 = Q(labels__name='A_LABEL')
f2 = ~Q(labels__name='B_LABEL')
qs = Event.objects.filter(f1) & Event.objects.filter(f2)
results = [int(obj.id) for obj in qs]
self.assertEqual(set(results), set([2,6]))
def test_qs_order_op(self):
# This does (A and B) or C
f1 = Q(labels__name='A_LABEL')
f2 = Q(labels__name='B_LABEL')
f3 = Q(labels__name='C_LABEL')
qs = Event.objects.filter(f1) & Event.objects.filter(f2) | Event.objects.filter(f3)
results = [int(obj.id) for obj in qs]
self.assertEqual(set(results), set([4,5,6,7,8]))
def test_qs_order_op2(self):
# This does A and (B or C)
f1 = Q(labels__name='A_LABEL')
f2 = Q(labels__name='B_LABEL')
f3 = Q(labels__name='C_LABEL')
qs = Event.objects.filter(f2) | Event.objects.filter(f3)
qs = Event.objects.filter(f1) & qs
results = [int(obj.id) for obj in qs]
self.assertEqual(set(results), set([5,6,8]))
......@@ -7,7 +7,7 @@
Relational and range queries can be made on event attributes.
<ul>
<li><code>instruments = "H1,L1,V1" &amp; far &lt; 1e-7</code></li>
<li><code>singleinspiral.mchirp &gt;= 1.6 &amp; eff_distance in 40.5,55 </code></li>
<li><code>singleinspiral.mchirp &gt;= 0.5 &amp; singleinspiral.eff_distance in 0.0,55 </code></li>
<li><code>(si.channel = "DMT-STRAIN" | si.channel = "DMT-PAIN") &amp; si.snr &lt; 5</code></li>
<li><code>mb.snr in 1,3 &amp; mb.central_freq &gt; 1000</code></li>
</ul>
......@@ -19,8 +19,7 @@
keyword optional.
<ul>
<li><code>899999000 .. 999999999</code></li>
<li><code>gpstime: 999999999</code></li>
<li><code>gpstime: 899999000 .. 999999999</code></li>
<li><code>gpstime: 899999000.0 .. 999999999.9</code></li>
</ul>
<h4>By Creation Time</h4>
......@@ -32,7 +31,6 @@
<ul>
<li><code>created: 2009-10-08 .. 2009-12-04 16:00:00</code></li>
<li><code>yesterday..now</code></li>
<li><code></code></li>
<li><code>created: 1 week ago .. now</code></li>
</ul>
......@@ -51,19 +49,21 @@
<li>CBC Burst</li>
<!-- <li>Inspiral CWB</li> -->
<li>group: Test pipeline: cwb</li>
<li>Burst Omega</li>
<li>Burst cwb</li>
</ul>
<h4>By Label</h4>
You may request only events that have a certain label. The <code>label:</code> keyword
is optional. Note that specifying multiple labels is an "OR" operation. More complex
expressiveness is on the 'todo' list.
You may request only events with a particular label or set of labels. The <code>label:</code> keyword
is optional. Label names can be combined with binary AND: '&amp;' or ','; or binary OR: '|'. For N labels,
there must be exactly N-1 binary operators. (Parentheses are not allowed.) Additionally, any of the labels
in a query string can be negated with '~' or '-'.
<ul>
<li>label: INJ</li>
<li>DQV LUMIN_GO</li>
<li>EM_READY &amp; ADVOK</li>
<li>H1OK | L1OK &amp; ~INJ &amp; ~DQV</li>
</ul>
Valid labels are:
cWB_s, cWB_r, EM_READY, SWIFT_NO, SWIFT_GO, LUMIN_NO, LUMIN_GO, DQV, INJ
Labels in current use are:
INJ, DQV, EM_READY, PE_READY, H1NO, H1OK, H1OPS, L1NO, L1OK, L1OPS, ADVREQ, ADVOK, ADVNO
<h4>By Submitter</h4>
......@@ -73,8 +73,8 @@
that ought to be remedied.
<ul>
<li>"waveburst"
<li>"gstlalcbc" "gdb-processor"
<li>submitter: "joss.whedon@ligo.org" submitter: "gdb_processor"
<li>"gstlalcbc" "gracedb.processor"
<li>submitter: "joss.whedon@ligo.org" submitter: "gracedb.processor"
</ul>
</div>
......@@ -12,4 +12,16 @@
</table>
<input type="submit" value="Submit"/>
</form>
{% if creating == 'Notification' %}
<p> <b>NOTE:</b>
For the label query, label names can be combined with binary AND: '&amp;' or ','; or binary OR: '|'.
For N labels, there must be exactly N-1 binary operators. (Parentheses are not
allowed.) Additionally, any of the labels in a query string can be negated with
'~' or '-'. Labels can either be selected with the select box at the top, or a query
can be specified, <i>but not both</i>.
</p>
{% endif %}
{% endblock %}
from django import forms
from models import Trigger, Contact
from gracedb.query import parseLabelQuery
from gracedb.pyparsing import ParseException
def triggerFormFactory(postdata=None, user=None):
class TF(forms.ModelForm):
farThresh = forms.FloatField(label='FAR Threshold (Hz)', required=False,
......@@ -8,6 +11,7 @@ def triggerFormFactory(postdata=None, user=None):
class Meta:
model = Trigger
exclude = ['user', 'triggerType']
widgets = {'label_query': forms.TextInput(attrs={'size': 50})}
contacts = forms.ModelMultipleChoiceField(
queryset=Contact.objects.filter(user=user),
......@@ -18,18 +22,27 @@ def triggerFormFactory(postdata=None, user=None):
# truth of (atypes or labels)
# and set field error attributes appropriately.
def clean(self):
cleaned_data = super(TF, self).clean()
label_query = self.cleaned_data['label_query']
if len(label_query) > 0:
# now try parsing it
try:
parseLabelQuery(label_query)
except ParseException:
raise forms.ValidationError("Invalid label query.")
return cleaned_data
if postdata is not None:
return TF(postdata)
else:
return TF()
class TriggerForm(forms.ModelForm):
class Meta:
model = Trigger
exclude = ['user', 'triggerType']
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('userprofile', '0002_auto_20150708_1134'),
]
operations = [
migrations.AddField(
model_name='trigger',
name='label_query',
field=models.CharField(max_length=100, blank=True),
),
]
......@@ -5,7 +5,6 @@ from gracedb.models import Label, Pipeline
from django.contrib.auth.models import User
#class Notification(models.Model):
# user = models.ForeignKey(User, null=False)
# onLabel = models.ManyToManyField(Label, blank=True)
......@@ -33,6 +32,7 @@ class Trigger(models.Model):
pipelines = models.ManyToManyField(Pipeline, blank=True)
contacts = models.ManyToManyField(Contact, blank=True)
farThresh = models.FloatField(blank=True, null=True)
label_query = models.CharField(max_length=100, blank=True)
def __unicode__(self):
return (u"%s %s: %s") % (
......@@ -45,9 +45,16 @@ class Trigger(models.Model):
thresh = ""
if self.farThresh:
thresh = " & (far < %s)" % self.farThresh
if self.label_query:
label_disp = self.label_query
else:
label_disp = "|".join([a.name for a in self.labels.all()]) or "creating"
return ("(%s) & (%s)%s -> %s") % (
"|".join([a.name for a in self.pipelines.all()]) or "any pipeline",
"|".join([a.name for a in self.labels.all()]) or "creating",
label_disp,
thresh,
",".join([x.desc for x in self.contacts.all()])
)
......@@ -2,6 +2,7 @@
from django.http import HttpResponse
from django.http import HttpResponseRedirect, HttpResponseNotFound
from django.http import Http404, HttpResponseForbidden
from django.http import HttpResponseBadRequest
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
......@@ -16,6 +17,10 @@ from gracedb.permission_utils import internal_user_required, lvem_user_required
from datetime import datetime
from gracedb.query import labelQuery
from gracedb.models import Label
from django.db.models import Q
# Let's let everybody onto the index view.
#@internal_user_required
def index(request):
......@@ -52,6 +57,28 @@ def create(request):
pipelines = form.cleaned_data['pipelines']
contacts = form.cleaned_data['contacts']
farThresh = form.cleaned_data['farThresh']
label_query = form.cleaned_data['label_query']
if len(label_query) > 0 and labels.count() > 0:
msg = "Cannot both select labels and define label query. Choose one or the other."
return HttpResponseBadRequest(msg)
# If we've got a label query defined for this trigger, then we want
# each label mentioned in the query to be listed in the events labels.
# It would be smarter to make sure the label isn't being negated, but
# we can just leave that for later.
if len(label_query) > 0:
toks = labelQuery(label_query, names=True)
f = Q()
for tok in toks:
# Note that all labels are being combined with OR
if isinstance(tok,Q):
f = f | tok
if len(f)==0:
return HttpResponseBadRequest("Please enter a valid label query.")
labels = Label.objects.filter(f)
if contacts and (labels or pipelines):
t.save() # Need an id before relations can be set.
......@@ -60,6 +87,7 @@ def create(request):
t.pipelines = pipelines
t.contacts = contacts
t.farThresh = farThresh
t.label_query = label_query
except:
t.delete()
t.save()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment