Commit 95dc6b7f authored by Mael Kervella's avatar Mael Kervella

Merge branch 'search' into 'master'

Search improved

Closes #32

See merge request rezo/re2o!29
parents 7b4432ef 57021ad5
...@@ -45,21 +45,32 @@ CHOICES_AFF = ( ...@@ -45,21 +45,32 @@ CHOICES_AFF = (
) )
def initial_choices(c): def initial_choices(choice_set):
"""Return the choices that should be activated by default for a """Return the choices that should be activated by default for a
given set of choices""" given set of choices"""
return [i[0] for i in c] return [i[0] for i in choice_set]
class SearchForm(Form): class SearchForm(Form):
"""The form for a simple search""" """The form for a simple search"""
q = forms.CharField(label='Search', max_length=100) q = forms.CharField(
label='Recherche',
help_text=(
'Utilisez « » et «,» pour spécifier différents mots, «"query"» '
'pour une recherche exacte et «\\» pour échapper un caractère.'
),
max_length=100
)
class SearchFormPlus(Form): class SearchFormPlus(Form):
"""The form for an advanced search (with filters)""" """The form for an advanced search (with filters)"""
q = forms.CharField( q = forms.CharField(
label='Search', label='Recherche',
help_text=(
'Utilisez « » et «,» pour spécifier différents mots, «"query"» '
'pour une recherche exacte et «\\» pour échapper un caractère.'
),
max_length=100, max_length=100,
required=False required=False
) )
......
...@@ -28,39 +28,39 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -28,39 +28,39 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block title %}Résultats de la recherche{% endblock %} {% block title %}Résultats de la recherche{% endblock %}
{% block content %} {% block content %}
{% if users_list %} {% if users %}
<h2>Résultats dans les utilisateurs</h2> <h2>Résultats dans les utilisateurs</h2>
{% include "users/aff_users.html" with users_list=users_list %} {% include "users/aff_users.html" with users_list=users %}
{% endif%} {% endif%}
{% if machines_list %} {% if machines %}
<h2>Résultats dans les machines : </h2> <h2>Résultats dans les machines : </h2>
{% include "machines/aff_machines.html" with machines_list=machines_list %} {% include "machines/aff_machines.html" with machines_list=machines %}
{% endif %} {% endif %}
{% if factures_list %} {% if factures %}
<h2>Résultats dans les factures : </h2> <h2>Résultats dans les factures : </h2>
{% include "cotisations/aff_cotisations.html" with facture_list=factures_list %} {% include "cotisations/aff_cotisations.html" with facture_list=factures %}
{% endif %} {% endif %}
{% if whitelists_list %} {% if whitelists %}
<h2>Résultats dans les accès à titre gracieux : </h2> <h2>Résultats dans les accès à titre gracieux : </h2>
{% include "users/aff_whitelists.html" with white_list=whitelists_list %} {% include "users/aff_whitelists.html" with white_list=whitelists %}
{% endif %} {% endif %}
{% if bans_list %} {% if bans %}
<h2>Résultats dans les banissements : </h2> <h2>Résultats dans les banissements : </h2>
{% include "users/aff_bans.html" with ban_list=bans_list %} {% include "users/aff_bans.html" with ban_list=bans %}
{% endif %} {% endif %}
{% if rooms_list %} {% if rooms %}
<h2>Résultats dans les chambres : </h2> <h2>Résultats dans les chambres : </h2>
{% include "topologie/aff_chambres.html" with room_list=rooms_list %} {% include "topologie/aff_chambres.html" with room_list=rooms %}
{% endif %} {% endif %}
{% if switch_ports_list %} {% if ports %}
<h2>Résultats dans les ports : </h2> <h2>Résultats dans les ports : </h2>
{% include "topologie/aff_port.html" with port_list=switch_ports_list %} {% include "topologie/aff_port.html" with port_list=ports %}
{% endif %} {% endif %}
{% if switches_list %} {% if switches %}
<h2>Résultats dans les switchs : </h2> <h2>Résultats dans les switchs : </h2>
{% include "topologie/aff_switch.html" with switch_list=switches_list %} {% include "topologie/aff_switch.html" with switch_list=switches %}
{% endif %} {% endif %}
{% if not users_list and not machines_list and not factures_list and not whitelists_list and not bans_list and not rooms_list and not switch_ports_list and not switches_list %} {% if not users and not machines and not factures and not whitelists and not bans and not rooms and not ports and not switches %}
<h3>Aucun résultat</h3> <h3>Aucun résultat</h3>
{% else %} {% else %}
<h6>(Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)</h6> <h6>(Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)</h6>
......
...@@ -57,7 +57,71 @@ def is_int(variable): ...@@ -57,7 +57,71 @@ def is_int(variable):
return True return True
def get_results(query, request, filters={}): def finish_results(results, col, order):
"""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
to the dict"""
results['users'] = SortTable.sort(
results['users'],
col,
order,
SortTable.USERS_INDEX
)
results['machines'] = SortTable.sort(
results['machines'],
col,
order,
SortTable.MACHINES_INDEX
)
results['factures'] = SortTable.sort(
results['factures'],
col,
order,
SortTable.COTISATIONS_INDEX
)
results['bans'] = SortTable.sort(
results['bans'],
col,
order,
SortTable.USERS_INDEX_BAN
)
results['whitelists'] = SortTable.sort(
results['whitelists'],
col,
order,
SortTable.USERS_INDEX_WHITE
)
results['rooms'] = SortTable.sort(
results['rooms'],
col,
order,
SortTable.TOPOLOGIE_INDEX_ROOM
)
results['ports'] = SortTable.sort(
results['ports'],
col,
order,
SortTable.TOPOLOGIE_INDEX_PORT
)
results['switches'] = SortTable.sort(
results['switches'],
col,
order,
SortTable.TOPOLOGIE_INDEX
)
options, _ = GeneralOption.objects.get_or_create()
max_result = options.search_display_page
for name, val in results.items():
results[name] = val.distinct()[:max_result]
results.update({'max_result': max_result})
return results
def search_single_word(word, filters, is_cableur, user_id,
start, end, user_state, aff):
""" Construct the correct filters to match differents fields of some models """ Construct the correct filters to match differents fields of some models
with the given query according to the given filters. with the given query according to the given filters.
The match field are either CharField or IntegerField that will be displayed The match field are either CharField or IntegerField that will be displayed
...@@ -65,111 +129,74 @@ def get_results(query, request, filters={}): ...@@ -65,111 +129,74 @@ def get_results(query, request, filters={}):
query). IntegerField are matched against the query only if it can be casted query). IntegerField are matched against the query only if it can be casted
to an int.""" to an int."""
start = filters.get('s', None)
end = filters.get('e', None)
user_state = filters.get('u', initial_choices(CHOICES_USER))
aff = filters.get('a', initial_choices(CHOICES_AFF))
options, _ = GeneralOption.objects.get_or_create()
max_result = options.search_display_page
results = {
'users_list': User.objects.none(),
'machines_list': Machine.objects.none(),
'factures_list': Facture.objects.none(),
'bans_list': Ban.objects.none(),
'whitelists_list': Whitelist.objects.none(),
'rooms_list': Room.objects.none(),
'switch_ports_list': Port.objects.none(),
'switches_list': Switch.objects.none()
}
# Users # Users
if '0' in aff: if '0' in aff:
filter_user_list = ( filter_users = (
Q( Q(
surname__icontains=query surname__icontains=word
) | Q( ) | Q(
adherent__name__icontains=query adherent__name__icontains=word
) | Q( ) | Q(
pseudo__icontains=query pseudo__icontains=word
) | Q( ) | Q(
club__room__name__icontains=query club__room__name__icontains=word
) | Q( ) | Q(
adherent__room__name__icontains=query adherent__room__name__icontains=word
) )
) & Q(state__in=user_state) ) & Q(state__in=user_state)
if not request.user.has_perms(('cableur',)): if not is_cableur:
filter_user_list &= Q(id=request.user.id) filter_users &= Q(id=user_id)
results['users_list'] = User.objects.filter(filter_user_list) filters['users'] |= filter_users
results['users_list'] = SortTable.sort(
results['users_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.USERS_INDEX
)
# Machines # Machines
if '1' in aff: if '1' in aff:
filter_machine_list = Q( filter_machines = Q(
name__icontains=query name__icontains=word
) | ( ) | (
Q( Q(
user__pseudo__icontains=query user__pseudo__icontains=word
) & Q( ) & Q(
user__state__in=user_state user__state__in=user_state
) )
) | Q( ) | Q(
interface__domain__name__icontains=query interface__domain__name__icontains=word
) | Q( ) | Q(
interface__domain__related_domain__name__icontains=query interface__domain__related_domain__name__icontains=word
) | Q( ) | Q(
interface__mac_address__icontains=query interface__mac_address__icontains=word
) | Q( ) | Q(
interface__ipv4__ipv4__icontains=query interface__ipv4__ipv4__icontains=word
)
if not request.user.has_perms(('cableur',)):
filter_machine_list &= Q(user__id=request.user.id)
results['machines_list'] = Machine.objects.filter(filter_machine_list)
results['machines_list'] = SortTable.sort(
results['machines_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.MACHINES_INDEX
) )
if not is_cableur:
filter_machines &= Q(user__id=user_id)
filters['machines'] |= filter_machines
# Factures # Factures
if '2' in aff: if '2' in aff:
filter_facture_list = Q( filter_factures = Q(
user__pseudo__icontains=query user__pseudo__icontains=word
) & Q( ) & Q(
user__state__in=user_state user__state__in=user_state
) )
if start is not None: if start is not None:
filter_facture_list &= Q(date__gte=start) filter_factures &= Q(date__gte=start)
if end is not None: if end is not None:
filter_facture_list &= Q(date__lte=end) filter_factures &= Q(date__lte=end)
results['factures_list'] = Facture.objects.filter(filter_facture_list) filters['factures'] |= filter_factures
results['factures_list'] = SortTable.sort(
results['factures_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.COTISATIONS_INDEX
)
# Bans # Bans
if '3' in aff: if '3' in aff:
date_filter = ( filter_bans = (
Q( Q(
user__pseudo__icontains=query user__pseudo__icontains=word
) & Q( ) & Q(
user__state__in=user_state user__state__in=user_state
) )
) | Q( ) | Q(
raison__icontains=query raison__icontains=word
) )
if start is not None: if start is not None:
date_filter &= ( filter_bans &= (
Q(date_start__gte=start) & Q(date_end__gte=start) Q(date_start__gte=start) & Q(date_end__gte=start)
) | ( ) | (
Q(date_start__lte=start) & Q(date_end__gte=start) Q(date_start__lte=start) & Q(date_end__gte=start)
...@@ -177,34 +204,28 @@ def get_results(query, request, filters={}): ...@@ -177,34 +204,28 @@ def get_results(query, request, filters={}):
Q(date_start__gte=start) & Q(date_end__lte=start) Q(date_start__gte=start) & Q(date_end__lte=start)
) )
if end is not None: if end is not None:
date_filter &= ( filter_bans &= (
Q(date_start__lte=end) & Q(date_end__lte=end) Q(date_start__lte=end) & Q(date_end__lte=end)
) | ( ) | (
Q(date_start__lte=end) & Q(date_end__gte=end) Q(date_start__lte=end) & Q(date_end__gte=end)
) | ( ) | (
Q(date_start__gte=end) & Q(date_end__lte=end) Q(date_start__gte=end) & Q(date_end__lte=end)
) )
results['bans_list'] = Ban.objects.filter(date_filter) filters['bans'] |= filter_bans
results['bans_list'] = SortTable.sort(
results['bans_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.USERS_INDEX_BAN
)
# Whitelists # Whitelists
if '4' in aff: if '4' in aff:
date_filter = ( filter_whitelists = (
Q( Q(
user__pseudo__icontains=query user__pseudo__icontains=word
) & Q( ) & Q(
user__state__in=user_state user__state__in=user_state
) )
) | Q( ) | Q(
raison__icontains=query raison__icontains=word
) )
if start is not None: if start is not None:
date_filter &= ( filter_whitelists &= (
Q(date_start__gte=start) & Q(date_end__gte=start) Q(date_start__gte=start) & Q(date_end__gte=start)
) | ( ) | (
Q(date_start__lte=start) & Q(date_end__gte=start) Q(date_start__lte=start) & Q(date_end__gte=start)
...@@ -212,100 +233,171 @@ def get_results(query, request, filters={}): ...@@ -212,100 +233,171 @@ def get_results(query, request, filters={}):
Q(date_start__gte=start) & Q(date_end__lte=start) Q(date_start__gte=start) & Q(date_end__lte=start)
) )
if end is not None: if end is not None:
date_filter &= ( filter_whitelists &= (
Q(date_start__lte=end) & Q(date_end__lte=end) Q(date_start__lte=end) & Q(date_end__lte=end)
) | ( ) | (
Q(date_start__lte=end) & Q(date_end__gte=end) Q(date_start__lte=end) & Q(date_end__gte=end)
) | ( ) | (
Q(date_start__gte=end) & Q(date_end__lte=end) Q(date_start__gte=end) & Q(date_end__lte=end)
) )
results['whitelists_list'] = Whitelist.objects.filter(date_filter) filters['whitelists'] |= filter_whitelists
results['whitelists_list'] = SortTable.sort(
results['whitelists_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.USERS_INDEX_WHITE
)
# Rooms # Rooms
if '5' in aff and request.user.has_perms(('cableur',)): if '5' in aff and is_cableur:
filter_rooms_list = Q( filter_rooms = Q(
details__icontains=query details__icontains=word
) | Q( ) | Q(
name__icontains=query name__icontains=word
) | Q( ) | Q(
port__details=query port__details=word
)
results['rooms_list'] = Room.objects.filter(filter_rooms_list)
results['rooms_list'] = SortTable.sort(
results['rooms_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX_ROOM
) )
filters['rooms'] |= filter_rooms
# Switch ports # Switch ports
if '6' in aff and request.user.has_perms(('cableur',)): if '6' in aff and is_cableur:
filter_ports_list = Q( filter_ports = Q(
room__name__icontains=query room__name__icontains=word
) | Q( ) | Q(
machine_interface__domain__name__icontains=query machine_interface__domain__name__icontains=word
) | Q( ) | Q(
related__switch__switch_interface__domain__name__icontains=query related__switch__switch_interface__domain__name__icontains=word
) | Q( ) | Q(
radius__icontains=query radius__icontains=word
) | Q( ) | Q(
vlan_force__name__icontains=query vlan_force__name__icontains=word
) | Q( ) | Q(
details__icontains=query details__icontains=word
) )
if is_int(query): if is_int(word):
filter_ports_list |= Q( filter_ports |= Q(
port=query port=word
) )
results['switch_ports_list'] = Port.objects.filter(filter_ports_list) filters['ports'] |= filter_ports
results['switch_ports_list'] = SortTable.sort(
results['switch_ports_list'],
request.GET.get('col'),
request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX_PORT
)
# Switches # Switches
if '7' in aff and request.user.has_perms(('cableur',)): if '7' in aff and is_cableur:
filter_switches_list = Q( filter_switches = Q(
switch_interface__domain__name__icontains=query switch_interface__domain__name__icontains=word
) | Q( ) | Q(
switch_interface__ipv4__ipv4__icontains=query switch_interface__ipv4__ipv4__icontains=word
) | Q( ) | Q(
location__icontains=query location__icontains=word
) | Q( ) | Q(
stack__name__icontains=query stack__name__icontains=word
) | Q( ) | Q(
model__reference__icontains=query model__reference__icontains=word
) | Q( ) | Q(
model__constructor__name__icontains=query model__constructor__name__icontains=word
) | Q( ) | Q(
details__icontains=query details__icontains=word
) )
if is_int(query): if is_int(word):
filter_switches_list |= Q( filter_switches |= Q(
number=query number=word
) | Q( ) | Q(
stack_member_id=query stack_member_id=word
) )
results['switches_list'] = Switch.objects.filter(filter_switches_list) filters['switches'] |= filter_switches
results['switches_list'] = SortTable.sort(
results['switches_list'], return filters
request.GET.get('col'),
request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX def get_words(query):
"""Function used to split the uery in different words to look for.
The rules are simple :
- anti-slash ('\\') is used to escape characters
- anything between quotation marks ('"') is kept intact (not
interpreted as separators) excepts anti-slashes used to escape
- spaces (' ') and commas (',') are used to separated words
"""
words = []
i = 0
keep_intact = False
escaping_char = False
for char in query:
if i >= len(words):
# We are starting a new word
words.append('')
if escaping_char:
# The last char war a \ so we escape this char
escaping_char = False
words[i] += char
continue
if char == '\\':
# We need to escape the next char
escaping_char = True
continue
if char == '"':
# Toogle the keep_intact state, if true, we are between two "
keep_intact = not keep_intact
continue
if keep_intact:
# If we are between two ", ignore separators
words[i] += char
continue
if char == ' ' or char == ',':
# 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
def get_results(query, request, params):
"""The main function of the search procedure. It gather the filters for
each of the different words of the query and concatenate them into a
single filter. Then it calls 'finish_results' and return the queryset of
objects to display as results"""
start = params.get('s', None)
end = params.get('e', None)
user_state = params.get('u', initial_choices(CHOICES_USER))
aff = params.get('a', initial_choices(CHOICES_AFF))
filters = {
'users': Q(),
'machines': Q(),
'factures': Q(),
'bans': Q(),
'whitelists': Q(),
'rooms': Q(),
'ports': Q(),
'switches': Q()
}
words = get_words(query)
for word in words:
filters = search_single_word(
word,
filters,
request.user.has_perms(('cableur',)),