Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

views.py 13.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 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.

Maël Kervella's avatar
Maël Kervella committed
23 24 25 26
"""The views for the search app, responsible for finding the matches
Augustin lemesle, Gabriel Détraz, Goulven Kermarec, Maël Kervella
Gplv2"""

27 28 29

from __future__ import unicode_literals

30
from django.shortcuts import render
chirac's avatar
chirac committed
31
from django.contrib.auth.decorators import login_required
32 33

from django.db.models import Q
34
from users.models import User, Adherent, Club, Ban, Whitelist
Maël Kervella's avatar
Maël Kervella committed
35
from machines.models import Machine
36
from cotisations.models import Cotisation
37
from topologie.models import Port, Switch, Room
38
from cotisations.models import Facture
39
from preferences.models import GeneralOption
40 41 42 43 44 45 46
from search.forms import (
    SearchForm,
    SearchFormPlus,
    CHOICES_USER,
    CHOICES_AFF,
    initial_choices
)
47
from re2o.utils import SortTable
48
from re2o.acl import can_view_all
49

50 51 52 53 54 55 56 57 58 59 60

def is_int(variable):
    """ Check if the variable can be casted to an integer """

    try:
        int(variable)
    except ValueError:
        return False
    else:
        return True

Maël Kervella's avatar
Maël Kervella committed
61

62
def finish_results(results, col, order):
Maël Kervella's avatar
Maël Kervella committed
63
    """Sort the results by applying filters and then limit them to the
64 65
    number of max results. Finally add the info of the nmax number of results
    to the dict"""
66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    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
    )

116
    max_result = GeneralOption.get_cached_value('search_display_page')
117 118 119 120 121 122 123
    for name, val in results.items():
        results[name] = val.distinct()[:max_result]
    results.update({'max_result': max_result})

    return results


Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
124
def search_single_word(word, filters, user,
Maël Kervella's avatar
Maël Kervella committed
125
                       start, end, user_state, aff):
126 127 128 129 130 131 132
    """ 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."""

133 134
    # Users
    if '0' in aff:
135
        filter_users = (
136
            Q(
137
                surname__icontains=word
138
            ) | Q(
139
                pseudo__icontains=word
140
            ) | Q(
141
                room__name__icontains=word
142
            ) | Q(
143 144 145
                email__icontains=word
            ) | Q(
                telephone__icontains=word
146 147
            )
        ) & Q(state__in=user_state)
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
148 149
        if not User.can_view_all(user)[0]:
            filter_users &= Q(id=user.id)
150
        filter_clubs = filter_users
151
        filter_users |= Q(name__icontains=word)
152
        filters['users'] |= filter_users
153
        filters['clubs'] |= filter_clubs
154 155 156

    # Machines
    if '1' in aff:
157 158
        filter_machines = Q(
            name__icontains=word
159 160
        ) | (
            Q(
161
                user__pseudo__icontains=word
162 163 164
            ) & Q(
                user__state__in=user_state
            )
165
        ) | Q(
166
            interface__domain__name__icontains=word
167
        ) | Q(
168
            interface__domain__related_domain__name__icontains=word
169
        ) | Q(
170
            interface__mac_address__icontains=word
171
        ) | Q(
172
            interface__ipv4__ipv4__icontains=word
173
        )
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
174 175
        if not Machine.can_view_all(user)[0]:
            filter_machines &= Q(user__id=user.id)
176
        filters['machines'] |= filter_machines
177 178 179

    # Factures
    if '2' in aff:
180 181
        filter_factures = Q(
            user__pseudo__icontains=word
182 183
        ) & Q(
            user__state__in=user_state
184
        )
Maël Kervella's avatar
Maël Kervella committed
185
        if start is not None:
186
            filter_factures &= Q(date__gte=start)
Maël Kervella's avatar
Maël Kervella committed
187
        if end is not None:
188 189
            filter_factures &= Q(date__lte=end)
        filters['factures'] |= filter_factures
190 191 192

    # Bans
    if '3' in aff:
193
        filter_bans = (
194
            Q(
195
                user__pseudo__icontains=word
196 197 198
            ) & Q(
                user__state__in=user_state
            )
199
        ) | Q(
200
            raison__icontains=word
201
        )
Maël Kervella's avatar
Maël Kervella committed
202
        if start is not None:
203
            filter_bans &= (
204 205 206 207 208 209
                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)
            )
Maël Kervella's avatar
Maël Kervella committed
210
        if end is not None:
211
            filter_bans &= (
212 213 214 215 216 217
                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)
            )
218
        filters['bans'] |= filter_bans
219 220 221

    # Whitelists
    if '4' in aff:
222
        filter_whitelists = (
223
            Q(
224
                user__pseudo__icontains=word
225 226 227
            ) & Q(
                user__state__in=user_state
            )
228
        ) | Q(
229
            raison__icontains=word
230
        )
Maël Kervella's avatar
Maël Kervella committed
231
        if start is not None:
232
            filter_whitelists &= (
233 234 235 236 237 238
                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)
            )
Maël Kervella's avatar
Maël Kervella committed
239
        if end is not None:
240
            filter_whitelists &= (
241 242 243 244 245 246
                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)
            )
247
        filters['whitelists'] |= filter_whitelists
248

249
    # Rooms
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
250
    if '5' in aff and Room.can_view_all(user):
251 252
        filter_rooms = Q(
            details__icontains=word
253
        ) | Q(
254
            name__icontains=word
255
        ) | Q(
256
            port__details=word
257
        )
258
        filters['rooms'] |= filter_rooms
259 260

    # Switch ports
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
261
    if '6' in aff and User.can_view_all(user):
262 263
        filter_ports = Q(
            room__name__icontains=word
264
        ) | Q(
265
            machine_interface__domain__name__icontains=word
266
        ) | Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
267
            related__switch__interface__domain__name__icontains=word
268
        ) | Q(
269
            custom_profile__name__icontains=word
270
        ) | Q(
271
            custom_profile__profil_default__icontains=word
272
        ) | Q(
273
            details__icontains=word
274
        )
275 276 277
        if is_int(word):
            filter_ports |= Q(
                port=word
278
            )
279
        filters['ports'] |= filter_ports
280

281
    # Switches
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
282
    if '7' in aff and Switch.can_view_all(user):
283
        filter_switches = Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
284
            interface__domain__name__icontains=word
285
        ) | Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
286
            interface__ipv4__ipv4__icontains=word
287
        ) | Q(
288
            switchbay__building__name__icontains=word
289
        ) | Q(
290
            stack__name__icontains=word
291
        ) | Q(
292
            model__reference__icontains=word
293
        ) | Q(
294
            model__constructor__name__icontains=word
295
        ) | Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
296
            interface__details__icontains=word
297
        )
298 299 300
        if is_int(word):
            filter_switches |= Q(
                number=word
301
            ) | Q(
302
                stack_member_id=word
303
            )
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
        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
Maël Kervella's avatar
Maël Kervella committed
343
        if char == ' ' or char == ',':
344 345 346 347 348 349
            # 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
Maël Kervella's avatar
Maël Kervella committed
350

351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
    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(),
367
        'clubs': Q(),
368 369 370 371 372 373 374 375 376 377 378 379 380 381
        '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,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
382
            request.user,
383 384 385 386
            start,
            end,
            user_state,
            aff
387 388
        )

389
    results = {
390 391
        'users': Adherent.objects.filter(filters['users']),
        'clubs': Club.objects.filter(filters['clubs']),
392 393 394 395 396 397 398 399
        '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'])
    }
400

401 402 403 404 405
    results = finish_results(
        results,
        request.GET.get('col'),
        request.GET.get('order')
    )
406
    results.update({'search_term': query})
407

408
    return results
Dalahro's avatar
Dalahro committed
409

Maël Kervella's avatar
Maël Kervella committed
410

chirac's avatar
chirac committed
411
@login_required
412
@can_view_all(User, Machine, Cotisation)
Dalahro's avatar
Dalahro committed
413
def search(request):
Maël Kervella's avatar
Maël Kervella committed
414
    """ La page de recherche standard """
415 416
    search_form = SearchForm(request.GET or None)
    if search_form.is_valid():
417 418 419 420 421 422 423 424 425
        return render(
            request,
            'search/index.html',
            get_results(
                search_form.cleaned_data.get('q', ''),
                request,
                search_form.cleaned_data
            )
        )
Maël Kervella's avatar
Maël Kervella committed
426 427
    return render(request, 'search/search.html', {'search_form': search_form})

Dalahro's avatar
Dalahro committed
428

chirac's avatar
chirac committed
429
@login_required
430
@can_view_all(User, Machine, Cotisation)
Dalahro's avatar
Dalahro committed
431
def searchp(request):
Maël Kervella's avatar
Maël Kervella committed
432
    """ La page de recherche avancée """
433 434
    search_form = SearchFormPlus(request.GET or None)
    if search_form.is_valid():
435 436 437 438 439 440 441 442 443
        return render(
            request,
            'search/index.html',
            get_results(
                search_form.cleaned_data.get('q', ''),
                request,
                search_form.cleaned_data
            )
        )
Maël Kervella's avatar
Maël Kervella committed
444
    return render(request, 'search/search.html', {'search_form': search_form})