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
                room__name__icontains=word
144 145
            )
        ) & Q(state__in=user_state)
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
146 147
        if not User.can_view_all(user)[0]:
            filter_users &= Q(id=user.id)
148
        filter_clubs = filter_users
149
        filter_users |= Q(name__icontains=word)
150
        filters['users'] |= filter_users
151
        filters['clubs'] |= filter_clubs
152 153 154

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

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

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

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

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

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

279
    # Switches
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
280
    if '7' in aff and Switch.can_view_all(user):
281
        filter_switches = Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
282
            interface__domain__name__icontains=word
283
        ) | Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
284
            interface__ipv4__ipv4__icontains=word
285
        ) | Q(
286
            switchbay__building__name__icontains=word
287
        ) | Q(
288
            stack__name__icontains=word
289
        ) | Q(
290
            model__reference__icontains=word
291
        ) | Q(
292
            model__constructor__name__icontains=word
293
        ) | Q(
Gabriel Detraz's avatar
Gabriel Detraz committed
294
            interface__details__icontains=word
295
        )
296 297 298
        if is_int(word):
            filter_switches |= Q(
                number=word
299
            ) | Q(
300
                stack_member_id=word
301
            )
302 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
        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
341
        if char == ' ' or char == ',':
342 343 344 345 346 347
            # 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
348

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

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

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

406
    return results
Dalahro's avatar
Dalahro committed
407

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

chirac's avatar
chirac committed
409
@login_required
410
@can_view_all(User, Machine, Cotisation)
Dalahro's avatar
Dalahro committed
411
def search(request):
Maël Kervella's avatar
Maël Kervella committed
412
    """ La page de recherche standard """
413 414
    search_form = SearchForm(request.GET or None)
    if search_form.is_valid():
415 416 417 418 419 420 421 422 423
        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
424 425
    return render(request, 'search/search.html', {'search_form': search_form})

Dalahro's avatar
Dalahro committed
426

chirac's avatar
chirac committed
427
@login_required
428
@can_view_all(User, Machine, Cotisation)
Dalahro's avatar
Dalahro committed
429
def searchp(request):
Maël Kervella's avatar
Maël Kervella committed
430
    """ La page de recherche avancée """
431 432
    search_form = SearchFormPlus(request.GET or None)
    if search_form.is_valid():
433 434 435 436 437 438 439 440 441
        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
442
    return render(request, 'search/search.html', {'search_form': search_form})