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

Merge branch 'label_logic'

parents 25cd78b0 e877e58e
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