Browse Source

Handle queries with "+" operators

room_building_search
Jean-Romain Garnier 6 years ago
parent
commit
7ba5ff8be6
  1. 139
      search/views.py

139
search/views.py

@ -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 = []
# Format: {
# "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
words = [] # Whether the query is between "
i = 0
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
# 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 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 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 continue
# If we haven't encountered any special case, add the char to the word # If we haven't encountered any special case, add the char to the word
words[i] += char current_query["text"].append(char)
return words 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)

Loading…
Cancel
Save