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 !

utils.py 12.1 KB
Newer Older
chirac's avatar
chirac committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
# -*- 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.

# -*- coding: utf-8 -*-
# David Sinquin, Gabriel Détraz, Goulven Kermarec
"""
Regroupe les fonctions transversales utiles

Fonction :
    - récupérer tous les utilisateurs actifs
    - récupérer toutes les machines
    - récupérer tous les bans
    etc
"""


from __future__ import unicode_literals

from django.utils import timezone
from django.db.models import Q
41
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
chirac's avatar
chirac committed
42

43 44
from cotisations.models import Cotisation, Facture, Vente
from machines.models import Interface, Machine
45
from users.models import Adherent, User, Ban, Whitelist
chirac's avatar
chirac committed
46

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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
# Mapping of srtftime format for better understanding
# https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior
datetime_mapping={
    '%a': '%a',
    '%A': '%A',
    '%w': '%w',
    '%d': 'dd',
    '%b': '%b',
    '%B': '%B',
    '%m': 'mm',
    '%y': 'yy',
    '%Y': 'yyyy',
    '%H': 'HH',
    '%I': 'HH(12h)',
    '%p': 'AMPM',
    '%M': 'MM',
    '%S': 'SS',
    '%f': 'µµ',
    '%z': 'UTC(+/-HHMM)',
    '%Z': 'UTC(TZ)',
    '%j': '%j',
    '%U': 'ww',
    '%W': 'ww',
    '%c': '%c',
    '%x': '%x',
    '%X': '%X',
    '%%': '%%',
}


def convert_datetime_format(format):
    i=0
    new_format = ""
    while i < len(format):
        if format[i] == '%':
            char = format[i:i+2]
            new_format += datetime_mapping.get(char, char)
            i += 2
        else:
            new_format += format[i]
            i += 1
    return new_format

chirac's avatar
chirac committed
90

91
def all_adherent(search_time=None):
chirac's avatar
chirac committed
92 93 94 95
    """ Fonction renvoyant tous les users adherents. Optimisee pour n'est
    qu'une seule requete sql
    Inspecte les factures de l'user et ses cotisation, regarde si elles
    sont posterieur à now (end_time)"""
96 97
    if search_time is None:
        search_time = timezone.now()
chirac's avatar
chirac committed
98 99 100
    return User.objects.filter(
        facture__in=Facture.objects.filter(
            vente__in=Vente.objects.filter(
101
                Q(type_cotisation='All') | Q(type_cotisation='Adhesion'),
chirac's avatar
chirac committed
102 103 104 105 106 107 108 109 110 111
                cotisation__in=Cotisation.objects.filter(
                    vente__in=Vente.objects.filter(
                        facture__in=Facture.objects.all().exclude(valid=False)
                    )
                ).filter(date_end__gt=search_time)
            )
        )
    ).distinct()


112
def all_baned(search_time=None):
chirac's avatar
chirac committed
113
    """ Fonction renvoyant tous les users bannis """
114 115
    if search_time is None:
        search_time = timezone.now()
chirac's avatar
chirac committed
116 117 118 119 120 121 122
    return User.objects.filter(
        ban__in=Ban.objects.filter(
            date_end__gt=search_time
        )
    ).distinct()


123
def all_whitelisted(search_time=None):
chirac's avatar
chirac committed
124
    """ Fonction renvoyant tous les users whitelistes """
125 126
    if search_time is None:
        search_time = timezone.now()
chirac's avatar
chirac committed
127 128 129 130 131 132 133
    return User.objects.filter(
        whitelist__in=Whitelist.objects.filter(
            date_end__gt=search_time
        )
    ).distinct()


134
def all_has_access(search_time=None):
chirac's avatar
chirac committed
135 136
    """  Renvoie tous les users beneficiant d'une connexion
    : user adherent ou whiteliste et non banni """
137 138
    if search_time is None:
        search_time = timezone.now()
chirac's avatar
chirac committed
139 140 141 142 143 144 145
    return User.objects.filter(
        Q(state=User.STATE_ACTIVE) &
        ~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) &
        (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) |
         Q(facture__in=Facture.objects.filter(
             vente__in=Vente.objects.filter(
                 cotisation__in=Cotisation.objects.filter(
146
                     Q(type_cotisation='All') | Q(type_cotisation='Connexion'),
chirac's avatar
chirac committed
147 148 149 150 151 152 153 154 155 156
                     vente__in=Vente.objects.filter(
                         facture__in=Facture.objects.all()
                         .exclude(valid=False)
                     )
                 ).filter(date_end__gt=search_time)
             )
         )))
    ).distinct()


157 158
def filter_active_interfaces(interface_set):
    """Filtre les machines autorisées à sortir sur internet dans une requête"""
Maël Kervella's avatar
Maël Kervella committed
159 160 161 162 163 164 165 166 167 168 169 170
    return (interface_set
            .filter(
                machine__in=Machine.objects.filter(
                    user__in=all_has_access()
                ).filter(active=True)
            ).select_related('domain')
            .select_related('machine')
            .select_related('type')
            .select_related('ipv4')
            .select_related('domain__extension')
            .select_related('ipv4__ip_type')
            .distinct())
chirac's avatar
chirac committed
171 172


173 174 175 176 177 178
def filter_complete_interfaces(interface_set):
    """Appel la fonction précédente avec un prefetch_related ipv6 en plus"""
    return filter_active_interfaces(interface_set).prefetch_related('ipv6list')


def all_active_interfaces(full=False):
179
    """Renvoie l'ensemble des machines autorisées à sortir sur internet """
180 181 182 183
    if full:
        return filter_complete_interfaces(Interface.objects)
    else:
        return filter_active_interfaces(Interface.objects)
184 185


186
def all_active_assigned_interfaces(full=False):
chirac's avatar
chirac committed
187 188
    """ Renvoie l'ensemble des machines qui ont une ipv4 assignées et
    disposant de l'accès internet"""
189
    return all_active_interfaces(full=full).filter(ipv4__isnull=False)
chirac's avatar
chirac committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203


def all_active_interfaces_count():
    """ Version light seulement pour compter"""
    return Interface.objects.filter(
        machine__in=Machine.objects.filter(
            user__in=all_has_access()
        ).filter(active=True)
    )


def all_active_assigned_interfaces_count():
    """ Version light seulement pour compter"""
    return all_active_interfaces_count().filter(ipv4__isnull=False)
204

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

206 207 208 209 210
class SortTable:
    """ Class gathering uselful stuff to sort the colums of a table, according
    to the column and order requested. It's used with a dict of possible
    values and associated model_fields """

211
    # All the possible possible values
212
    # The naming convention is based on the URL or the views function
213 214 215 216
    # The syntax to describe the sort to apply is a dict where the keys are
    # the url value and the values are a list of model field name to use to
    # order the request. They are applied in the order they are given.
    # A 'default' might be provided to specify what to do if the requested col
Maël Kervella's avatar
Maël Kervella committed
217 218
    # doesn't match any keys.

219
    USERS_INDEX = {
220 221 222 223
        'user_name': ['name'],
        'user_surname': ['surname'],
        'user_pseudo': ['pseudo'],
        'user_room': ['room'],
224
        'default': ['state', 'pseudo']
225 226
    }
    USERS_INDEX_BAN = {
227 228 229
        'ban_user': ['user__pseudo'],
        'ban_start': ['date_start'],
        'ban_end': ['date_end'],
230
        'default': ['-date_end']
231 232
    }
    USERS_INDEX_WHITE = {
233 234 235
        'white_user': ['user__pseudo'],
        'white_start': ['date_start'],
        'white_end': ['date_end'],
236
        'default': ['-date_end']
237
    }
238 239 240 241
    USERS_INDEX_SCHOOL = {
        'school_name': ['name'],
        'default': ['name']
    }
242
    MACHINES_INDEX = {
243
        'machine_name': ['name'],
244
        'default': ['pk']
245 246
    }
    COTISATIONS_INDEX = {
247 248 249
        'cotis_user': ['user__pseudo'],
        'cotis_paiement': ['paiement__moyen'],
        'cotis_date': ['date'],
250
        'cotis_id': ['id'],
251
        'default': ['-date']
252 253
    }
    COTISATIONS_CONTROL = {
254
        'control_name': ['user__adherent__name'],
255 256 257 258 259
        'control_surname': ['user__surname'],
        'control_paiement': ['paiement'],
        'control_date': ['date'],
        'control_valid': ['valid'],
        'control_control': ['control'],
260 261
        'control_id': ['id'],
        'control_user-id': ['user__id'],
262
        'default': ['-date']
263 264
    }
    TOPOLOGIE_INDEX = {
265 266
        'switch_dns': ['interface__domain__name'],
        'switch_ip': ['interface__ipv4__ipv4'],
267
        'switch_loc': ['switchbay__name'],
268 269
        'switch_ports': ['number'],
        'switch_stack': ['stack__name'],
270
        'default': ['switchbay', 'stack', 'stack_member_id']
271 272
    }
    TOPOLOGIE_INDEX_PORT = {
273 274 275 276 277 278
        'port_port': ['port'],
        'port_room': ['room__name'],
        'port_interface': ['machine_interface__domain__name'],
        'port_related': ['related__switch__name'],
        'port_radius': ['radius'],
        'port_vlan': ['vlan_force__name'],
279
        'default': ['port']
280 281
    }
    TOPOLOGIE_INDEX_ROOM = {
282
        'room_name': ['name'],
283
        'default': ['name']
284
    }
285 286 287 288
    TOPOLOGIE_INDEX_BUILDING = {
        'building_name': ['name'],
        'default': ['name']
    }
289
    TOPOLOGIE_INDEX_BORNE = {
290 291 292 293
        'ap_name': ['interface__domain__name'],
        'ap_ip': ['interface__ipv4__ipv4'],
        'ap_mac': ['interface__mac_address'],
        'default': ['interface__domain__name']
294
    }
295
    TOPOLOGIE_INDEX_STACK = {
296 297
        'stack_name': ['name'],
        'stack_id': ['stack_id'],
298
        'default': ['stack_id'],
299
    }
300
    TOPOLOGIE_INDEX_MODEL_SWITCH = {
301
        'model-switch_name': ['reference'],
Maël Kervella's avatar
Maël Kervella committed
302
        'model-switch_contructor': ['constructor__name'],
303 304
        'default': ['reference'],
    }
305 306 307 308 309
    TOPOLOGIE_INDEX_SWITCH_BAY = {
        'switch-bay_name': ['name'],
        'switch-bay_building': ['building__name'],
        'default': ['name'],
    }
310
    TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH = {
311
        'constructor-switch_name': ['name'],
312 313
        'default': ['name'],
    }
314
    LOGS_INDEX = {
315
        'sum_date': ['revision__date_created'],
316
        'default': ['-revision__date_created'],
317 318
    }
    LOGS_STATS_LOGS = {
319 320
        'logs_author': ['user__name'],
        'logs_date': ['date_created'],
321
        'default': ['-date_created']
322
    }
323 324

    @staticmethod
325
    def sort(request, col, order, values):
326 327
        """ Check if the given values are possible and add .order_by() and
        a .reverse() as specified according to those values """
328 329 330 331
        fields = values.get(col, None)
        if not fields:
            fields = values.get('default', [])
        request = request.order_by(*fields)
332
        if values.get(col, None) and order == 'desc':
333
            return request.reverse()
334
        else:
335
            return request
336

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

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
def re2o_paginator(request, query_set, pagination_number):
    """Paginator script for list display in re2o.
    :request:
    :query_set: Query_set to paginate
    :pagination_number: Number of entries to display"""
    paginator = Paginator(query_set, pagination_number)
    page = request.GET.get('page')
    try:
        results = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        results = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        results = paginator.page(paginator.num_pages)
    return results
354

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

356 357 358 359 360 361 362 363
def remove_user_room(room):
    """ Déménage de force l'ancien locataire de la chambre """
    try:
        user = Adherent.objects.get(room=room)
    except Adherent.DoesNotExist:
        return
    user.room = None
    user.save()
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379


def get_input_formats_help_text(input_formats):
    """Returns a help text about the possible input formats"""
    if len(input_formats) > 1:
        help_text_template="Format: {main} {more}"
    else:
        help_text_template="Format: {main}"
    more_text_template="<i class=\"fa fa-question-circle\" title=\"{}\"></i>"
    help_text = help_text_template.format(
        main=convert_datetime_format(input_formats[0]),
        more=more_text_template.format(
            '\n'.join(map(convert_datetime_format, input_formats))
        )
    )
    return help_text