8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-23 20:03:11 +00:00

Handle queries with "+" operators

This commit is contained in:
Jean-Romain Garnier 2020-02-18 12:02:08 +01:00 committed by Gabriel Detraz
parent fe11867ba9
commit 5eac7f6614

View file

@ -62,11 +62,22 @@ def is_int(variable):
return True return True
def filter_fields():
"""Return the list of fields the search applies to"""
return ["users", "clubs", "machines", "factures", "bans", "whitelists", "rooms", "ports", "switches"]
def empty_filters():
"""Build empty filters used by Django"""
filters = [Q() for f in filter_fields()]
def finish_results(request, results, col, order): def finish_results(request, results, col, order):
"""Sort the results by applying filters and then limit them to the """Sort the results by applying filters and then limit them to the
number of max results. Finally add the info of the nmax number of results number of max results. Finally add the info of the nmax number of results
to the dict""" to the dict"""
results["users"] += results["clubs"]
results["users"] = SortTable.sort( results["users"] = SortTable.sort(
results["users"], col, order, SortTable.USERS_INDEX results["users"], col, order, SortTable.USERS_INDEX
) )
@ -121,12 +132,6 @@ def search_single_word(word, filters, user, start, end, user_state, aff):
) )
filter_users = (filter_clubs | Q(name__icontains=word)) filter_users = (filter_clubs | Q(name__icontains=word))
if len(word.split(" ")) >= 2:
# Assume the room is in 1 word, and the building may be in multiple words
building = " ".join(word.split(" ")[:-1])
room = word.split(" ")[-1]
filter_users |= (Q(room__name__icontains=room) & Q(room__building__name__icontains=building))
if not User.can_view_all(user)[0]: if not User.can_view_all(user)[0]:
filter_clubs &= Q(id=user.id) filter_clubs &= Q(id=user.id)
filter_users &= Q(id=user.id) filter_users &= Q(id=user.id)
@ -211,12 +216,6 @@ def search_single_word(word, filters, user, start, end, user_state, aff):
Q(details__icontains=word) | Q(name__icontains=word) | Q(port__details=word) Q(details__icontains=word) | Q(name__icontains=word) | Q(port__details=word)
) )
if len(word.split(" ")) >= 2:
# Assume the room is in 1 word, and the building may be in multiple words
building = " ".join(word.split(" ")[:-1])
room = word.split(" ")[-1]
filter_rooms |= (Q(name__icontains=room) & Q(building__name__icontains=building))
filters["rooms"] |= filter_rooms filters["rooms"] |= filter_rooms
# Switch ports # Switch ports
@ -252,7 +251,7 @@ def search_single_word(word, filters, user, start, end, user_state, aff):
def apply_filters(filters, user, aff): def apply_filters(filters, user, aff):
""" Apply the filters constructed by search_single_word. """ Apply the filters constructed by search_single_query.
It also takes into account the visual filters defined during It also takes into account the visual filters defined during
the search query. the search query.
""" """
@ -305,53 +304,117 @@ def apply_filters(filters, user, aff):
return results return results
def get_words(query): def search_single_query(query, filters, user, start, end, user_state, aff):
"""Function used to split the uery in different words to look for. """ Handle different queries an construct the correct filters using
The rules are simple : search_single_word"""
if query["operator"] == "+":
# Special queries with "+" operators should use & rather than |
for q in query["subqueries"]:
# Construct an independent filter for each subquery
subfilters = empty_filters()
subfilters = search_single_word(q, subfilters, user, start, end, user_state, aff)
# Apply the new filter
for field in filter_fields():
filters[field] &= subfilters[field]
return filters
# Handle standard queries
q = query["text"]
return search_single_word(q, filters, user, start, end, user_state, aff)
def create_queries(query):
"""Function used to split the query in different words to look for.
The rules are the following :
- anti-slash ('\\') is used to escape characters - anti-slash ('\\') is used to escape characters
- anything between quotation marks ('"') is kept intact (not - anything between quotation marks ('"') is kept intact (not
interpreted as separators) excepts anti-slashes used to escape interpreted as separators) excepts anti-slashes used to escape
Values in between quotation marks are not searched accross
multiple field in the database (contrary to +)
- spaces (' ') and commas (',') are used to separated words - spaces (' ') and commas (',') are used to separated words
- "+" signs are used as "and" operators
""" """
# A dict representing the different queries extracted from the user's text
queries = []
words = [] # Format: {
i = 0 # "text": "", # Content of the query
# "operator": None, # Whether a special char ("+") was used
# "subqueries": None # When splitting the query in subparts (ex when using "+")
# }
current_query = None
# Whether the query is between "
keep_intact = False keep_intact = False
# Whether the previous char was a \
escaping_char = False escaping_char = False
for char in query: for char in query:
if i >= len(words): if current_query is None:
# We are starting a new word # We are starting a new word
words.append("") current_query = { "text": "", "operator": None, "subqueries": None }
if escaping_char: if escaping_char:
# The last char war a \ so we escape this char # The last char war a \ so we escape this char
escaping_char = False escaping_char = False
words[i] += char current_query["text"] += char
continue continue
if char == "\\": if char == "\\":
# We need to escape the next char # We need to escape the next char
escaping_char = True escaping_char = True
continue continue
if char == '"': if char == '"':
# Toogle the keep_intact state, if true, we are between two " # Toogle the keep_intact state, if true, we are between two "
keep_intact = not keep_intact keep_intact = not keep_intact
continue continue
if keep_intact: if keep_intact:
# If we are between two ", ignore separators # If we are between two ", ignore separators
words[i] += char current_query["text"] += char
continue continue
if char == "+": if char == "+":
# If we encouter a + outside of ", we replace it with a space # Can't sart a query with a "+", consider it escaped
words[i] += " " if len(current_query["text"]) == 0:
current_query["text"] = char
continue continue
# Build a slightly more complicate data structure
# This is need for queries like '"A B"+C'
if current_query["operator"] is None:
current_query["operator"] = "+"
current_query["subqueries"] = []
current_query["subqueries"].append(current_query["text"])
current_query["text"] = ""
continue
if char == " " or char == ",": if char == " " or char == ",":
# If we encouter a separator outside of ", we create a new word # If we encouter a separator outside of ", we create a new word
if words[i] is not "":
i += 1
continue
# If we haven't encountered any special case, add the char to the word
words[i] += char
return words if len(current_query["text"]) == 0:
# Discard empty queries
continue
if current_query["operator"] is not None:
# If we were building a special structure, finish building it
current_query["subqueries"].append(current_query["text"])
current_query["text"] = ""
# Save the query and start a new one
queries.append(current_query)
current_query = None
continue
# If we haven't encountered any special case, add the char to the word
current_query["text"].append(char)
return queries
def get_results(query, request, params): def get_results(query, request, params):
@ -365,22 +428,12 @@ def get_results(query, request, params):
user_state = params.get("u", initial_choices(CHOICES_USER)) user_state = params.get("u", initial_choices(CHOICES_USER))
aff = params.get("a", initial_choices(CHOICES_AFF)) aff = params.get("a", initial_choices(CHOICES_AFF))
filters = { filters = empty_filters()
"users": Q(),
"clubs": Q(),
"machines": Q(),
"factures": Q(),
"bans": Q(),
"whitelists": Q(),
"rooms": Q(),
"ports": Q(),
"switches": Q(),
}
words = get_words(query) queries = create_queries(query)
for word in words: for query in queries:
filters = search_single_word( filters = search_single_query(
word, filters, request.user, start, end, user_state, aff query, filters, request.user, start, end, user_state, aff
) )
results = apply_filters(filters, request.user, aff) results = apply_filters(filters, request.user, aff)