diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..95aaebe3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,21 @@ +--- +image: debian:stretch +stages: + - lint + +lint: + stage: lint + variables: + LANG: 'en_US.UTF-8' + LC_ALL: 'en_US.UTF-8' + LANGUAGE: 'en_US.UTF-8' + script: + - apt-get -qq update + - DEBIAN_FRONTEND=noninteractive apt-get -qq install -y locales python3-pip python3-django + - sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && dpkg-reconfigure --frontend=noninteractive locales && update-locale LANG=en_US.UTF-8 + - pip3 install -q pylint-django + - pylint --load-plugins pylint_django cotisations machines re2o logs topologie preferences search users || if [ $? -ne 1 ]; then exit 0; else exit 1; fi + + + + diff --git a/README.md b/README.md index 023d186a..fb234ea1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ dessus, qui accèdent à la base de donnée en passant par django (ex : dhcp), e chargeant la liste de toutes les mac-ip, ou la liste des mac-ip autorisées sur le réseau (adhérent à jour de cotisation). -#Installation +# Installation ## Installation des dépendances diff --git a/cotisations/forms.py b/cotisations/forms.py index 354da1f1..7725016c 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -56,8 +56,10 @@ class NewFactureForm(ModelForm): self.fields['banque'].empty_label = "Non renseigné" self.fields['paiement'].empty_label = "Séléctionner\ un moyen de paiement" - self.fields['paiement'].widget.attrs['data-cheque'] = Paiement.objects\ - .filter(type_paiement=1).first().id + paiement_list = Paiement.objects.filter(type_paiement=1) + if paiement_list: + self.fields['paiement'].widget\ + .attrs['data-cheque'] = paiement_list.first().id class Meta: model = Facture diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 16f3f084..88bee71e 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -154,12 +154,13 @@ def authorize(data): else: nas_type = None if not nas_type or nas_type.port_access_mode == '802.1X': - user = data.get('User-Name', '') + user = data.get('User-Name', '').decode('utf-8', errors='replace') user = user.split('@', 1)[0] mac = data.get('Calling-Station-Id', '') result, log, password = check_user_machine_and_register(nas_type, user, mac) logger.info(log.encode('utf-8')) - + logger.info(user.encode('utf-8')) + if not result: return radiusd.RLM_MODULE_REJECT else: diff --git a/re2o/templatetags/url_insert_param.py b/re2o/templatetags/url_insert_param.py index 7e37a71b..90f7a0a6 100644 --- a/re2o/templatetags/url_insert_param.py +++ b/re2o/templatetags/url_insert_param.py @@ -36,7 +36,9 @@ def url_insert_param(url="", **kwargs): Return the URL with some specific parameters inserted into the query part. If a URL has already some parameters, those requested will be modified if already exisiting or will be added and the other parameters - will stay unmodified. + will stay unmodified. If parameters with the same name are already in the + URL and a value is specified for this parameter, it will replace all + existing parameters. **Tag name**:: @@ -82,19 +84,22 @@ def url_insert_param(url="", **kwargs): # Get existing parameters in the url params = {} if '?' in url: - url, params = url.split('?', maxsplit=1) - params = { - p[:p.find('=')]: p[p.find('=')+1:] for p in params.split('&') - } + url, parameters = url.split('?', maxsplit=1) + for parameter in parameters.split('&'): + p_name, p_value = parameter.split('=', maxsplit=1) + if p_name not in params: + params[p_name] = [] + params[p_name].append(p_value) # Add the request parameters to the list of parameters for key, value in kwargs.items(): - params[key] = value + params[key] = [value] # Write the url url += '?' - for param, value in params.items(): - url += str(param) + '=' + str(value) + '&' + for param, value_list in params.items(): + for value in value_list: + url += str(param) + '=' + str(value) + '&' # Remove the last '&' (or '?' if no parameters) return url[:-1] diff --git a/re2o/utils.py b/re2o/utils.py index 0fa6a84c..a6e5c851 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -248,7 +248,7 @@ class SortTable: if not fields: fields = values.get('default', []) request = request.order_by(*fields) - if order == 'desc': + if values.get(col, None) and order == 'desc': return request.reverse() else: return request diff --git a/search/admin.py b/search/admin.py index bcdc4f1d..decd096a 100644 --- a/search/admin.py +++ b/search/admin.py @@ -21,8 +21,8 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""The field used in the admin view for the search app""" + from __future__ import unicode_literals -from django.contrib import admin - # Register your models here. diff --git a/search/forms.py b/search/forms.py index fa43be55..b0668ef9 100644 --- a/search/forms.py +++ b/search/forms.py @@ -20,21 +20,83 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""The forms used by the search app""" + from __future__ import unicode_literals -from django.db.models import Q -from simple_search import BaseSearchForm +from django import forms +from django.forms import Form -from users.models import User, School +CHOICES_USER = ( + ('0', 'Actifs'), + ('1', 'Désactivés'), + ('2', 'Archivés'), +) -class UserSearchForm(BaseSearchForm): - class Meta: - base_qs = User.objects - search_fields = ('^name', 'description', 'specifications', '=id') +CHOICES_AFF = ( + ('0', 'Utilisateurs'), + ('1', 'Machines'), + ('2', 'Factures'), + ('3', 'Bannissements'), + ('4', 'Accès à titre gracieux'), + ('5', 'Chambres'), + ('6', 'Ports'), + ('7', 'Switchs'), +) - # assumes a fulltext index has been defined on the fields - # 'name,description,specifications,id' - fulltext_indexes = ( - ('name', 2), # name matches are weighted higher - ('name,description,specifications,id', 1), - ) + +def initial_choices(choice_set): + """Return the choices that should be activated by default for a + given set of choices""" + return [i[0] for i in choice_set] + + +class SearchForm(Form): + """The form for a simple search""" + 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): + """The form for an advanced search (with filters)""" + 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, + required=False + ) + u = forms.MultipleChoiceField( + label="Filtre utilisateurs", + required=False, + widget=forms.CheckboxSelectMultiple, + choices=CHOICES_USER, + initial=initial_choices(CHOICES_USER) + ) + a = forms.MultipleChoiceField( + label="Filtre affichage", + required=False, + widget=forms.CheckboxSelectMultiple, + choices=CHOICES_AFF, + initial=initial_choices(CHOICES_AFF) + ) + s = forms.DateField( + required=False, + label="Date de début", + help_text='DD/MM/YYYY', + input_formats=['%d/%m/%Y'] + ) + e = forms.DateField( + required=False, + help_text='DD/MM/YYYY', + input_formats=['%d/%m/%Y'], + label="Date de fin" + ) diff --git a/search/models.py b/search/models.py deleted file mode 100644 index 8d1fa0e4..00000000 --- a/search/models.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- mode: python; coding: utf-8 -*- -# Re2o est un logiciel d'administration développé initiallement au rezometz. Il -# se veut agnostique au réseau considéré, de manière à être installable en -# quelques clics. -# -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Goulven Kermarec -# Copyright © 2017 Augustin Lemesle -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from __future__ import unicode_literals - -from django.db import models -from django import forms -from django.forms import Form -from django.forms import ModelForm - -CHOICES = ( - ('0', 'Actifs'), - ('1', 'Désactivés'), - ('2', 'Archivés'), -) - -CHOICES2 = ( - (1, 'Active'), - ("", 'Désactivée'), -) - -CHOICES3 = ( - ('0', 'Utilisateurs'), - ('1', 'Machines'), - ('2', 'Factures'), - ('3', 'Bannissements'), - ('4', 'Accès à titre gracieux'), - ('6', 'Switchs'), - ('5', 'Ports'), -) - - -class SearchForm(Form): - search_field = forms.CharField(label = 'Search', max_length = 100) - -class SearchFormPlus(Form): - search_field = forms.CharField(label = 'Search', max_length = 100, required=False) - filtre = forms.MultipleChoiceField(label="Filtre utilisateurs", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES) - connexion = forms.MultipleChoiceField(label="Filtre connexion", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES2) - affichage = forms.MultipleChoiceField(label="Filtre affichage", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES3) - date_deb = forms.DateField(required=False, label="Date de début", help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y']) - date_fin = forms.DateField(required=False, help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y'], label="Date de fin") diff --git a/search/templates/search/index.html b/search/templates/search/index.html index 859193fb..98658bfb 100644 --- a/search/templates/search/index.html +++ b/search/templates/search/index.html @@ -28,38 +28,43 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}Résultats de la recherche{% endblock %} {% block content %} - {% if users_list %} + {% if users %}

Résultats dans les utilisateurs

- {% include "users/aff_users.html" with users_list=users_list %} + {% include "users/aff_users.html" with users_list=users %} {% endif%} - {% if machines_list %} + {% if machines %}

Résultats dans les machines :

- {% include "machines/aff_machines.html" with machines_list=machines_list %} + {% include "machines/aff_machines.html" with machines_list=machines %} {% endif %} - {% if facture_list %} + {% if factures %}

Résultats dans les factures :

- {% include "cotisations/aff_cotisations.html" with facture_list=facture_list %} + {% include "cotisations/aff_cotisations.html" with facture_list=factures %} {% endif %} - {% if white_list %} + {% if whitelists %}

Résultats dans les accès à titre gracieux :

- {% include "users/aff_whitelists.html" with white_list=white_list %} + {% include "users/aff_whitelists.html" with white_list=whitelists %} {% endif %} - {% if ban_list %} + {% if bans %}

Résultats dans les banissements :

- {% include "users/aff_bans.html" with ban_list=ban_list %} + {% include "users/aff_bans.html" with ban_list=bans %} {% endif %} - {% if switch_list %} -

Résultats dans les switchs :

- {% include "topologie/aff_switch.html" with switch_list=switch_list %} + {% if rooms %} +

Résultats dans les chambres :

+ {% include "topologie/aff_chambres.html" with room_list=rooms %} {% endif %} - {% if port_list %} + {% if ports %}

Résultats dans les ports :

- {% include "topologie/aff_port.html" with port_list=port_list %} + {% include "topologie/aff_port.html" with port_list=ports %} {% endif %} - {% if not ban_list and not interfaces_list and not users_list and not facture_list and not white_list and not port_list and not switch_list%} + {% if switches %} +

Résultats dans les switchs :

+ {% include "topologie/aff_switch.html" with switch_list=switches %} + {% endif %} + {% if not users and not machines and not factures and not whitelists and not bans and not rooms and not ports and not switches %}

Aucun résultat

+ {% else %} +
(Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)
{% endif %} -
(Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)



diff --git a/search/templates/search/search.html b/search/templates/search/search.html index adb5dd92..7ae5d56d 100644 --- a/search/templates/search/search.html +++ b/search/templates/search/search.html @@ -28,11 +28,22 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}Recherche{% endblock %} {% block content %} -{% bootstrap_form_errors searchform %} +{% bootstrap_form_errors search_form %} -
- {% csrf_token %} - {% bootstrap_form searchform %} + + {% bootstrap_field search_form.q %} + {% if search_form.u %} + {% include "buttons/multiple_checkbox_alt.html" with field=search_form.u %} + {% endif %} + {% if search_form.a %} + {% include "buttons/multiple_checkbox_alt.html" with field=search_form.a %} + {% endif %} + {% if search_form.s %} + {% bootstrap_field search_form.s %} + {% endif %} + {% if search_form.e %} + {% bootstrap_field search_form.e %} + {% endif %} {% bootstrap_button "Search" button_type="submit" icon="search" %}

diff --git a/search/urls.py b/search/urls.py index 3b16fcd1..dc1490e5 100644 --- a/search/urls.py +++ b/search/urls.py @@ -20,6 +20,8 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""The urls used by the search app""" + from __future__ import unicode_literals from django.conf.urls import url @@ -28,5 +30,5 @@ from . import views urlpatterns = [ url(r'^$', views.search, name='search'), - url(r'^avance/$', views.searchp, name='searchp'), + url(r'^advanced/$', views.searchp, name='searchp'), ] diff --git a/search/views.py b/search/views.py index 16c365d8..a561dd11 100644 --- a/search/views.py +++ b/search/views.py @@ -20,115 +20,418 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# App de recherche pour re2o -# Augustin lemesle, Gabriel Détraz, Goulven Kermarec -# Gplv2 +"""The views for the search app, responsible for finding the matches +Augustin lemesle, Gabriel Détraz, Goulven Kermarec, Maël Kervella +Gplv2""" + from __future__ import unicode_literals from django.shortcuts import render -from django.shortcuts import get_object_or_404 -from django.template.context_processors import csrf -from django.template import Context, RequestContext, loader from django.contrib.auth.decorators import login_required from django.db.models import Q from users.models import User, Ban, Whitelist -from machines.models import Machine, Interface -from topologie.models import Port, Switch +from machines.models import Machine +from topologie.models import Port, Switch, Room from cotisations.models import Facture -from search.models import SearchForm, SearchFormPlus from preferences.models import GeneralOption +from search.forms import ( + SearchForm, + SearchFormPlus, + CHOICES_USER, + CHOICES_AFF, + initial_choices +) +from re2o.utils import SortTable -def form(ctx, template, request): - c = ctx - c.update(csrf(request)) - return render(request, template, c) -def search_result(search, type, request): - date_deb = None - date_fin = None - states=[] - co=[] - aff=[] - if(type): - aff = search.cleaned_data['affichage'] - co = search.cleaned_data['connexion'] - states = search.cleaned_data['filtre'] - date_deb = search.cleaned_data['date_deb'] - date_fin = search.cleaned_data['date_fin'] - date_query = Q() - if aff==[]: - aff = ['0','1','2','3','4','5','6'] - if date_deb != None: - date_query = date_query & Q(date__gte=date_deb) - if date_fin != None: - date_query = date_query & Q(date__lte=date_fin) - search = search.cleaned_data['search_field'] - query1 = Q() - for s in states: - query1 = query1 | Q(state = s) - - connexion = [] - - recherche = {'users_list': None, 'machines_list' : [], 'facture_list' : None, 'ban_list' : None, 'white_list': None, 'port_list': None, 'switch_list': None} +def is_int(variable): + """ Check if the variable can be casted to an integer """ - if request.user.has_perms(('cableur',)): - query = Q(user__pseudo__icontains = search) | Q(user__adherent__name__icontains = search) | Q(user__surname__icontains = search) + try: + int(variable) + except ValueError: + return False else: - query = (Q(user__pseudo__icontains = search) | Q(user__adherent__name__icontains = search) | Q(user__surname__icontains = search)) & Q(user = request.user) + return True - for i in aff: - if i == '0': - query_user_list = Q(adherent__room__name__icontains = search) | Q(club__room__name__icontains = search) | Q(pseudo__icontains = search) | Q(adherent__name__icontains = search) | Q(surname__icontains = search) & query1 - if request.user.has_perms(('cableur',)): - recherche['users_list'] = User.objects.filter(query_user_list).order_by('state', 'surname').distinct() - else : - recherche['users_list'] = User.objects.filter(query_user_list & Q(id=request.user.id)).order_by('state', 'surname').distinct() - if i == '1': - query_machine_list = Q(machine__user__pseudo__icontains = search) | Q(machine__user__adherent__name__icontains = search) | Q(machine__user__surname__icontains = search) | Q(mac_address__icontains = search) | Q(ipv4__ipv4__icontains = search) | Q(domain__name__icontains = search) | Q(domain__related_domain__name__icontains = search) - if request.user.has_perms(('cableur',)): - data = Interface.objects.filter(query_machine_list).distinct() - else: - data = Interface.objects.filter(query_machine_list & Q(machine__user__id = request.user.id)).distinct() - for d in data: - recherche['machines_list'].append(d.machine) - if i == '2': - recherche['facture_list'] = Facture.objects.filter(query & date_query).distinct() - if i == '3': - recherche['ban_list'] = Ban.objects.filter(query).distinct() - if i == '4': - recherche['white_list'] = Whitelist.objects.filter(query).distinct() - if i == '5': - recherche['port_list'] = Port.objects.filter(details__icontains = search).distinct() - if not request.user.has_perms(('cableur',)): - recherche['port_list'] = None - if i == '6': - recherche['switch_list'] = Switch.objects.filter(details__icontains = search).distinct() - if not request.user.has_perms(('cableur',)): - recherche['switch_list'] = None - options, created = GeneralOption.objects.get_or_create() - search_display_page = options.search_display_page +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""" - for r in recherche: - if recherche[r] != None: - recherche[r] = recherche[r][:search_display_page] + 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 + ) - recherche.update({'max_result': search_display_page}) + 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 + with the given query according to the given filters. + The match field are either CharField or IntegerField that will be displayed + on the results page (else, one might not see why a result has matched the + query). IntegerField are matched against the query only if it can be casted + to an int.""" + + # Users + if '0' in aff: + filter_users = ( + Q( + surname__icontains=word + ) | Q( + adherent__name__icontains=word + ) | Q( + pseudo__icontains=word + ) | Q( + club__room__name__icontains=word + ) | Q( + adherent__room__name__icontains=word + ) + ) & Q(state__in=user_state) + if not is_cableur: + filter_users &= Q(id=user_id) + filters['users'] |= filter_users + + # Machines + if '1' in aff: + filter_machines = Q( + name__icontains=word + ) | ( + Q( + user__pseudo__icontains=word + ) & Q( + user__state__in=user_state + ) + ) | Q( + interface__domain__name__icontains=word + ) | Q( + interface__domain__related_domain__name__icontains=word + ) | Q( + interface__mac_address__icontains=word + ) | Q( + interface__ipv4__ipv4__icontains=word + ) + if not is_cableur: + filter_machines &= Q(user__id=user_id) + filters['machines'] |= filter_machines + + # Factures + if '2' in aff: + filter_factures = Q( + user__pseudo__icontains=word + ) & Q( + user__state__in=user_state + ) + if start is not None: + filter_factures &= Q(date__gte=start) + if end is not None: + filter_factures &= Q(date__lte=end) + filters['factures'] |= filter_factures + + # Bans + if '3' in aff: + filter_bans = ( + Q( + user__pseudo__icontains=word + ) & Q( + user__state__in=user_state + ) + ) | Q( + raison__icontains=word + ) + if start is not None: + filter_bans &= ( + Q(date_start__gte=start) & Q(date_end__gte=start) + ) | ( + Q(date_start__lte=start) & Q(date_end__gte=start) + ) | ( + Q(date_start__gte=start) & Q(date_end__lte=start) + ) + if end is not None: + filter_bans &= ( + Q(date_start__lte=end) & Q(date_end__lte=end) + ) | ( + Q(date_start__lte=end) & Q(date_end__gte=end) + ) | ( + Q(date_start__gte=end) & Q(date_end__lte=end) + ) + filters['bans'] |= filter_bans + + # Whitelists + if '4' in aff: + filter_whitelists = ( + Q( + user__pseudo__icontains=word + ) & Q( + user__state__in=user_state + ) + ) | Q( + raison__icontains=word + ) + if start is not None: + filter_whitelists &= ( + Q(date_start__gte=start) & Q(date_end__gte=start) + ) | ( + Q(date_start__lte=start) & Q(date_end__gte=start) + ) | ( + Q(date_start__gte=start) & Q(date_end__lte=start) + ) + if end is not None: + filter_whitelists &= ( + Q(date_start__lte=end) & Q(date_end__lte=end) + ) | ( + Q(date_start__lte=end) & Q(date_end__gte=end) + ) | ( + Q(date_start__gte=end) & Q(date_end__lte=end) + ) + filters['whitelists'] |= filter_whitelists + + # Rooms + if '5' in aff and is_cableur: + filter_rooms = Q( + details__icontains=word + ) | Q( + name__icontains=word + ) | Q( + port__details=word + ) + filters['rooms'] |= filter_rooms + + # Switch ports + if '6' in aff and is_cableur: + filter_ports = Q( + room__name__icontains=word + ) | Q( + machine_interface__domain__name__icontains=word + ) | Q( + related__switch__switch_interface__domain__name__icontains=word + ) | Q( + radius__icontains=word + ) | Q( + vlan_force__name__icontains=word + ) | Q( + details__icontains=word + ) + if is_int(word): + filter_ports |= Q( + port=word + ) + filters['ports'] |= filter_ports + + # Switches + if '7' in aff and is_cableur: + filter_switches = Q( + switch_interface__domain__name__icontains=word + ) | Q( + switch_interface__ipv4__ipv4__icontains=word + ) | Q( + location__icontains=word + ) | Q( + stack__name__icontains=word + ) | Q( + model__reference__icontains=word + ) | Q( + model__constructor__name__icontains=word + ) | Q( + details__icontains=word + ) + if is_int(word): + filter_switches |= Q( + number=word + ) | Q( + stack_member_id=word + ) + filters['switches'] |= filter_switches + + return filters + + +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',)), + request.user.id, + start, + end, + user_state, + aff + ) + + results = { + 'users': User.objects.filter(filters['users']), + 'machines': Machine.objects.filter(filters['machines']), + 'factures': Facture.objects.filter(filters['factures']), + 'bans': Ban.objects.filter(filters['bans']), + 'whitelists': Whitelist.objects.filter(filters['whitelists']), + 'rooms': Room.objects.filter(filters['rooms']), + 'ports': Port.objects.filter(filters['ports']), + 'switches': Switch.objects.filter(filters['switches']) + } + + results = finish_results( + results, + request.GET.get('col'), + request.GET.get('order') + ) + results.update({'search_term': query}) + + return results - return recherche @login_required def search(request): - search = SearchForm(request.POST or None) - if search.is_valid(): - return form(search_result(search, False, request), 'search/index.html',request) - return form({'searchform' : search}, 'search/search.html', request) + """ La page de recherche standard """ + search_form = SearchForm(request.GET or None) + if search_form.is_valid(): + return render( + request, + 'search/index.html', + get_results( + search_form.cleaned_data.get('q', ''), + request, + search_form.cleaned_data + ) + ) + return render(request, 'search/search.html', {'search_form': search_form}) + @login_required def searchp(request): - search = SearchFormPlus(request.POST or None) - if search.is_valid(): - return form(search_result(search, True, request), 'search/index.html',request) - return form({'searchform' : search}, 'search/search.html', request) + """ La page de recherche avancée """ + search_form = SearchFormPlus(request.GET or None) + if search_form.is_valid(): + return render( + request, + 'search/index.html', + get_results( + search_form.cleaned_data.get('q', ''), + request, + search_form.cleaned_data + ) + ) + return render(request, 'search/search.html', {'search_form': search_form}) diff --git a/templates/base.html b/templates/base.html index d0d5e99a..7a886abc 100644 --- a/templates/base.html +++ b/templates/base.html @@ -73,10 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %}