views.py 12.9 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
Dalahro's avatar
Dalahro committed
34
from users.models import User, Ban, Whitelist
Maël Kervella's avatar
Maël Kervella committed
35
from machines.models import Machine
36
from topologie.models import Port, Switch, Room
37
from cotisations.models import Facture
38
from preferences.models import GeneralOption
39 40 41 42 43 44 45
from search.forms import (
    SearchForm,
    SearchFormPlus,
    CHOICES_USER,
    CHOICES_AFF,
    initial_choices
)
46
from re2o.utils import SortTable
47

48 49 50 51 52 53 54 55 56 57 58

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

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

59 60 61 62
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"""
63

64 65 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 116 117 118 119 120 121 122
    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, start, end, user_state, aff):
123 124 125 126 127 128 129
    """ 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."""

130 131
    # Users
    if '0' in aff:
132
        filter_users = (
133
            Q(
134
                surname__icontains=word
135
            ) | Q(
136
                adherent__name__icontains=word
137
            ) | Q(
138
                pseudo__icontains=word
139
            ) | Q(
140
                club__room__name__icontains=word
141
            ) | Q(
142
                adherent__room__name__icontains=word
143 144
            )
        ) & Q(state__in=user_state)
145 146 147
        if not is_cableur:
            filter_users &= Q(id=request.user.id)
        filters['users'] |= filter_users
148 149 150

    # Machines
    if '1' in aff:
151 152
        filter_machines = Q(
            name__icontains=word
153 154
        ) | (
            Q(
155
                user__pseudo__icontains=word
156 157 158
            ) & Q(
                user__state__in=user_state
            )
159
        ) | Q(
160
            interface__domain__name__icontains=word
161
        ) | Q(
162
            interface__domain__related_domain__name__icontains=word
163
        ) | Q(
164
            interface__mac_address__icontains=word
165
        ) | Q(
166
            interface__ipv4__ipv4__icontains=word
167
        )
168 169 170
        if not is_cableur:
            filter_machines &= Q(user__id=request.user.id)
        filters['machines'] |= filter_machines
171 172 173

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

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

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

243
    # Rooms
244 245 246
    if '5' in aff and is_cableur:
        filter_rooms = Q(
            details__icontains=word
247
        ) | Q(
248
            name__icontains=word
249
        ) | Q(
250
            port__details=word
251
        )
252
        filters['rooms'] |= filter_rooms
253 254

    # Switch ports
255 256 257
    if '6' in aff and is_cableur:
        filter_ports = Q(
            room__name__icontains=word
258
        ) | Q(
259
            machine_interface__domain__name__icontains=word
260
        ) | Q(
261
            related__switch__switch_interface__domain__name__icontains=word
262
        ) | Q(
263
            radius__icontains=word
264
        ) | Q(
265
            vlan_force__name__icontains=word
266
        ) | Q(
267
            details__icontains=word
268
        )
269 270 271
        if is_int(word):
            filter_ports |= Q(
                port=word
272
            )
273
        filters['ports'] |= filter_ports
274

275
    # Switches
276 277 278
    if '7' in aff and is_cableur:
        filter_switches = Q(
            switch_interface__domain__name__icontains=word
279
        ) | Q(
280
            switch_interface__ipv4__ipv4__icontains=word
281
        ) | Q(
282
            location__icontains=word
283
        ) | Q(
284
            stack__name__icontains=word
285
        ) | Q(
286
            model__reference__icontains=word
287
        ) | Q(
288
            model__constructor__name__icontains=word
289
        ) | Q(
290
            details__icontains=word
291
        )
292 293 294
        if is_int(word):
            filter_switches |= Q(
                number=word
295
            ) | Q(
296
                stack_member_id=word
297
            )
298 299 300 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 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
        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
            print( 'escaped '+char+' -> '+words[i] )
            continue
        if char == '\\':
            # We need to escape the next char
            print( 'escaping '+char+' -> '+words[i] )
            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
        print(words)
        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)
    print( words )
    for word in words:
        filters = search_single_word(
            word,
            filters,
            request.user.has_perms(('cableur',)),
            start,
            end,
            user_state,
            aff
384 385
        )

386 387 388 389 390 391 392 393 394 395
    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'])
    }
396

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

404
    return results
Dalahro's avatar
Dalahro committed
405

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

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

Dalahro's avatar
Dalahro committed
423

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