Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ d5a41c9b

History | View | Annotate | Download (16.3 KB)

1 19e88cb8 Thomas ANDREJAK
# -*- coding: utf-8 -*-
2 65383903 Francois POIROTTE
# vim:set expandtab tabstop=4 shiftwidth=4:
3 a77de887 Francois POIROTTE
################################################################################
4
#
5 25892058 Francois POIROTTE
# Copyright (C) 2007-2013 CS-SI
6 a77de887 Francois POIROTTE
#
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License version 2 as
9
# published by the Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
################################################################################
20
21 19e88cb8 Thomas ANDREJAK
"""Gestion de la requête, des plugins et de l'affichage du Vigiboard"""
22
23 7365fb51 Francois POIROTTE
from time import mktime
24
25 e181e86c Francois POIROTTE
from tg import config, tmpl_context, request, url
26 7365fb51 Francois POIROTTE
from pylons.i18n import ugettext as _
27 b2346a00 Francois POIROTTE
from paste.deploy.converters import asbool
28
29 6ab72614 Vincent QUEMENER
from sqlalchemy import not_, and_, asc, desc
30 8b2edebe Aurelien BOMPARD
from sqlalchemy.sql.expression import null as expr_null, union_all
31 cf3c2494 Vincent QUEMENER
from sqlalchemy.orm import contains_eager
32 7365fb51 Francois POIROTTE
33 e7e3d45e Francois POIROTTE
from vigilo.models.session import DBSession
34
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
35 916e4b79 Francois POIROTTE
    Host, LowLevelService, StateName, UserSupItem
36 e181e86c Francois POIROTTE
from vigiboard.widgets.edit_event import EditEventForm
37 86662bc9 Francois POIROTTE
from vigiboard.controllers.plugins import VigiboardRequestPlugin, INNER, ITEMS
38 7365fb51 Francois POIROTTE
39 19e88cb8 Thomas ANDREJAK
class VigiboardRequest():
40 86c3ae23 Francois POIROTTE
    """
41
    Classe gérant la génération de la requête finale,
42
    le préformatage des événements et celui des historiques
43
    """
44
45 5a845c93 Vincent QUEMENER
    def __init__(self, user, mask_closed_events=True, search=None, sort=None, order=None):
46 19e88cb8 Thomas ANDREJAK
        """
47 54644278 Francois POIROTTE
        Initialisation de l'objet qui effectue les requêtes de VigiBoard
48
        sur la base de données.
49
        Cet objet est responsable de la vérification des droits de
50
        l'utilisateur sur les données manipulées.
51 5a845c93 Vincent QUEMENER

52
        @param user: Nom de l'utilisateur cherchant à afficher les événements.
53
        @type  user: C{str}
54
        @param mask_closed_events: Booléen indiquant si l'on souhaite masquer les
55
            événements fermés ou non.
56
        @type  mask_closed_events: C{boolean}
57
        @param search: Dictionnaire contenant les critères de recherche.
58
        @type  search: C{dict}
59
        @param sort: Colonne de tri; vide en l'absence de tri.
60
        @type  sort: C{unicode}
61
        @param order: Ordre du tri ("asc" ou "desc"); vide en l'absence de tri.
62
        @type  order: C{unicode}
63

64 19e88cb8 Thomas ANDREJAK
        """
65
66 0f0e32ed Francois POIROTTE
        # Permet s'appliquer des filtres de recherche aux sous-requêtes.
67
        self.subqueries = []
68 19e88cb8 Thomas ANDREJAK
        self.generaterq = False
69 24d74687 Francois POIROTTE
70 86662bc9 Francois POIROTTE
        # Éléments à retourner (SELECT ...)
71
        self.table = []
72
73
        # Tables sur lesquelles porte la récupération (JOIN)
74
        self.join = []
75
76
        # Options à ajouter la requête
77
        self.option = []
78
79
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
80
        self.outerjoin = []
81
82
        # Critères de filtrage (WHERE)
83
        self.filter = []
84
85
        # Regroupements (GROUP BY)
86
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
87
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
88
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
89
        self.groupby = [
90
            StateName.order,
91
            Event.timestamp,
92 8ba2de75 Francois POIROTTE
            CorrEvent.ack,
93 86662bc9 Francois POIROTTE
            CorrEvent.priority,
94
            StateName.statename,
95
        ]
96
97
        self.req = DBSession
98
        self.plugin = []
99
        self.events = []
100
101 73119f8a Francois POIROTTE
        # Si l'utilisateur est privilégié, il a accès
102
        # à tous les hôtes/services sans restriction.
103
        if config.is_manager.is_met(request.environ):
104 180b869a Vincent QUEMENER
            # Sélection de tous les services de la BDD.
105 0f0e32ed Francois POIROTTE
            lls_query = DBSession.query(
106 180b869a Vincent QUEMENER
                LowLevelService.idservice.label("idsupitem"),
107
                LowLevelService.servicename.label("servicename"),
108
                Host.name.label("hostname"),
109
            ).join(
110
                (Host, Host.idhost == LowLevelService.idhost),
111 0f0e32ed Francois POIROTTE
            ).distinct()
112 180b869a Vincent QUEMENER
113
            # Sélection de tous les hôtes de la BDD.
114 0f0e32ed Francois POIROTTE
            host_query = DBSession.query(
115 180b869a Vincent QUEMENER
                Host.idhost.label("idsupitem"),
116
                expr_null().label("servicename"),
117
                Host.name.label("hostname"),
118 032d0f30 Vincent QUEMENER
            ).distinct()
119 0f0e32ed Francois POIROTTE
120
            # Application des filtres des plugins si nécessaire.
121
            if search is not None:
122
                # On tire ici partie du fait que les listes sont passées
123
                # par référence dans les fonctions.
124
                subqueries = [lls_query, host_query]
125 8b2edebe Aurelien BOMPARD
                for _plugin, instance in config.get('columns_plugins', []):
126 86662bc9 Francois POIROTTE
                    instance.handle_search_fields(
127
                        self, search, INNER, subqueries)
128 0f0e32ed Francois POIROTTE
                lls_query = subqueries[0]
129
                host_query = subqueries[1]
130 180b869a Vincent QUEMENER
131
            # Union des deux sélections précédentes
132
            self.items = union_all(
133 0f0e32ed Francois POIROTTE
                lls_query,
134
                host_query,
135 180b869a Vincent QUEMENER
                correlate=False
136
            ).alias()
137
138
        # Sinon, on ne récupère que les hôtes/services auquel il a accès.
139
        else:
140 916e4b79 Francois POIROTTE
            items = DBSession.query(
141 180b869a Vincent QUEMENER
                UserSupItem.idsupitem,
142
                UserSupItem.servicename,
143
                UserSupItem.hostname,
144
            ).filter(
145
                UserSupItem.username == user.user_name
146 0f0e32ed Francois POIROTTE
            ).distinct()
147 180b869a Vincent QUEMENER
148 0f0e32ed Francois POIROTTE
            # Application des filtres des plugins si nécessaire.
149
            if search is not None:
150
                # On tire ici partie du fait que les listes sont passées
151
                # par référence dans les fonctions.
152
                subqueries = [items]
153 8b2edebe Aurelien BOMPARD
                for _plugin, instance in config.get('columns_plugins', []):
154 86662bc9 Francois POIROTTE
                    instance.handle_search_fields(
155
                        self, search, INNER, subqueries)
156 0f0e32ed Francois POIROTTE
                items = subqueries[0]
157 916e4b79 Francois POIROTTE
158 86662bc9 Francois POIROTTE
            # Permet d'avoir le même format que pour l'autre requête.
159 0f0e32ed Francois POIROTTE
            self.items = items.subquery()
160 8484b8bd Francois POIROTTE
161 5a845c93 Vincent QUEMENER
        # Tris (ORDER BY)
162
        # Permet de répondre aux exigences suivantes :
163
        # - VIGILO_EXIG_VIGILO_BAC_0050
164
        # - VIGILO_EXIG_VIGILO_BAC_0060
165
        self.orderby = []
166
        if sort:
167
            for _plugin, instance in config.get('columns_plugins', []):
168
                criterion = instance.get_sort_criterion(self, sort)
169
                if criterion is not None:
170
                    if order == 'asc':
171
                        self.orderby.append(asc(criterion))
172
                    else:
173
                        self.orderby.append(desc(criterion))
174
175
        # Permet de définir le sens de tri pour la priorité.
176
        if config['vigiboard_priority_order'] == 'asc':
177
            priority_order = asc(CorrEvent.priority)
178
        else:
179
            priority_order = desc(CorrEvent.priority)
180
181
        self.orderby.extend([
182
            asc(CorrEvent.ack),                             # État acquittement
183
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
184
            priority_order,                                 # Priorité ITIL
185
        ])
186
187
        if asbool(config.get('state_first', True)):
188
            self.orderby.extend([
189
                desc(StateName.order),                      # Etat courant
190
                desc(Event.timestamp),                      # Timestamp
191
            ])
192
        else:
193
            self.orderby.extend([
194
                desc(Event.timestamp),                      # Timestamp
195
                desc(StateName.order),                      # Etat courant
196
            ])
197
198 86662bc9 Francois POIROTTE
        if search is not None:
199
            # 2nde passe pour les filtres : self.items est désormais défini.
200 8b2edebe Aurelien BOMPARD
            for _plugin, instance in config.get('columns_plugins', []):
201 86662bc9 Francois POIROTTE
                instance.handle_search_fields(self, search, ITEMS, subqueries)
202 911069bc Francois POIROTTE
203 54644278 Francois POIROTTE
        if mask_closed_events:
204
            self.filter.append(
205
                # On masque les événements avec l'état OK
206 8ba2de75 Francois POIROTTE
                # et traités (ack == CorrEvent.ACK_CLOSED).
207 54644278 Francois POIROTTE
                not_(and_(
208
                    StateName.statename.in_([u'OK', u'UP']),
209 8ba2de75 Francois POIROTTE
                    CorrEvent.ack == CorrEvent.ACK_CLOSED
210 54644278 Francois POIROTTE
                ))
211
            )
212 8484b8bd Francois POIROTTE
213 19e88cb8 Thomas ANDREJAK
214
    def add_plugin(self, *argv):
215
        """
216
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
217
        """
218 df66bc2c Francois POIROTTE
        for i in argv:
219 19e88cb8 Thomas ANDREJAK
            if isinstance(i, VigiboardRequestPlugin):
220 df66bc2c Francois POIROTTE
                if i.table:
221 19e88cb8 Thomas ANDREJAK
                    self.add_table(*i.table)
222 df66bc2c Francois POIROTTE
                if i.join:
223 19e88cb8 Thomas ANDREJAK
                    self.add_join(*i.join)
224 df66bc2c Francois POIROTTE
                if i.outerjoin:
225 19e88cb8 Thomas ANDREJAK
                    self.add_outer_join(*i.outerjoin)
226 df66bc2c Francois POIROTTE
                if i.filter:
227 19e88cb8 Thomas ANDREJAK
                    self.add_filter(*i.filter)
228 65383903 Francois POIROTTE
                if i.groupby:
229 19e88cb8 Thomas ANDREJAK
                    self.add_group_by(*i.groupby)
230 df66bc2c Francois POIROTTE
                if i.orderby:
231 19e88cb8 Thomas ANDREJAK
                    self.add_order_by(*i.orderby)
232
                self.plugin.append(i)
233
234
    def generate_request(self):
235
        """
236
        Génération de la requête avec l'ensemble des données stockées
237
        et la place dans la variable rq de la classe
238
        """
239 911069bc Francois POIROTTE
        if self.generaterq:
240
            return
241
242 65383903 Francois POIROTTE
        for plugin in config['columns_plugins']:
243
            self.add_plugin(plugin)
244 bc94248f Francois POIROTTE
245 b00c0ea7 Francois POIROTTE
        # Toutes les requêtes ont besoin de récupérer l'état courant
246
        # de l'événement.
247 5d20c2c5 Francois POIROTTE
        self.join.append((StateName, StateName.idstatename == \
248
                                        Event.current_state))
249 b00c0ea7 Francois POIROTTE
250
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
251
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
252
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
253
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
254 911069bc Francois POIROTTE
        self.add_group_by(*self.table)
255
256 19e88cb8 Thomas ANDREJAK
        # query et join ont besoin de referrence
257
        self.req = self.req.query(*self.table)
258
        self.req = self.req.join(*self.join)
259 cf3c2494 Vincent QUEMENER
        self.req = self.req.options(*self.option)
260 19e88cb8 Thomas ANDREJAK
261
        # le reste, non
262
        for i in self.outerjoin:
263
            self.req = self.req.outerjoin(i)
264
        for i in self.filter:
265
            self.req = self.req.filter(i)
266
        for i in self.groupby:
267
            self.req = self.req.group_by(i)
268
        for i in self.orderby:
269
            self.req = self.req.order_by(i)
270 911069bc Francois POIROTTE
        self.generaterq = True
271
272 19e88cb8 Thomas ANDREJAK
    def num_rows(self):
273
        """
274
        Retourne le nombre de lignes de la requête.
275
        Si celle-ci n'est pas encore générée, on le fait.
276

277 0bd9c069 Francois POIROTTE
        @return: Nombre de ligne
278 19e88cb8 Thomas ANDREJAK
        """
279
280 911069bc Francois POIROTTE
        self.generate_request()
281 19e88cb8 Thomas ANDREJAK
        return self.req.count()
282
283
    def add_table(self, *argv):
284
        """
285
        Ajoute une ou plusieurs tables/élément d'une table à
286
        la requête.
287

288
        @param argv: Liste des tables à ajouter
289
        """
290 65383903 Francois POIROTTE
291 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
292
        # des tables.
293 65383903 Francois POIROTTE
294 19e88cb8 Thomas ANDREJAK
        for i in argv :
295
            for j in self.table:
296
                if str(i) == str(j):
297
                    break
298
            self.table.append(i)
299
300
    def add_join(self, *argv):
301
        """
302
        Ajoute une ou plusieurs jointures à
303
        la requête.
304

305
        @param argv: Liste des jointures à ajouter
306
        """
307 65383903 Francois POIROTTE
308 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
309
        # des jointures.
310 65383903 Francois POIROTTE
311 19e88cb8 Thomas ANDREJAK
        for i in argv:
312
            for j in self.join:
313
                if str(i) == str(j):
314
                    break
315
            self.join.append(i)
316
317 cf3c2494 Vincent QUEMENER
    def add_option(self, *argv):
318
        """
319
        Ajoute une ou plusieurs options à la requête.
320

321
        @param argv: Liste des options à ajouter
322
        """
323
324
        # On vérifie qu'il n'y a pas de doublons
325
        # dans la liste finale des options.
326
327
        for i in argv:
328
            for j in self.option:
329
                if str(i) == str(j):
330
                    break
331
            self.option.append(i)
332
333
    def add_contains_eager(self, relation):
334
        """
335
        Ajoute une option de type contains_eager à la
336
        requête pour la relation passée en paramètre.
337
        """
338
        self.add_option(contains_eager(relation))
339
340 19e88cb8 Thomas ANDREJAK
    def add_outer_join(self, *argv):
341
        """
342
        Ajoute une ou plusieurs jointures externes à
343
        la requête.
344

345
        @param argv: Liste des jointures externes à ajouter
346
        """
347 65383903 Francois POIROTTE
348 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
349
        # des jointures externes.
350 65383903 Francois POIROTTE
351 19e88cb8 Thomas ANDREJAK
        for i in argv:
352
            for j in self.outerjoin:
353
                if str(i) == str(j):
354
                    break
355 65383903 Francois POIROTTE
            self.outerjoin.append(i)
356 19e88cb8 Thomas ANDREJAK
357
    def add_filter(self, *argv):
358
        """
359
        Ajoute un ou plusieurs filtres à la requête.
360

361
        @param argv: Liste des filtres à ajouter
362
        """
363 65383903 Francois POIROTTE
364 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
365
        # des filtres.
366 65383903 Francois POIROTTE
367 19e88cb8 Thomas ANDREJAK
        for i in argv:
368
            for j in self.filter:
369
                if str(i) == str(j):
370
                    break
371
            self.filter.append(i)
372
373
    def add_group_by(self, *argv):
374
        """
375
        Ajoute un ou plusieurs groupements à la requête.
376

377
        @param argv: Liste des groupements à ajouter
378
        """
379 65383903 Francois POIROTTE
380 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
381
        # des groupements.
382 65383903 Francois POIROTTE
383 19e88cb8 Thomas ANDREJAK
        for i in argv:
384
            for j in self.groupby:
385 df66bc2c Francois POIROTTE
                try:
386
                    if str(i) == str(j):
387
                        break
388
                # SQLAlchemy lève cette exception pour certains attributes,
389
                # par exemple les attributs définis avec synonym().
390
                except AttributeError:
391
                    pass
392 19e88cb8 Thomas ANDREJAK
            self.groupby.append(i)
393
394
    def add_order_by(self, *argv):
395
        """
396
        Ajoute un ou plusieurs orders à la requête.
397

398
        @param argv: Liste des ordres à ajouter
399
        """
400 65383903 Francois POIROTTE
401 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
402
        # des ordres.
403 65383903 Francois POIROTTE
404 19e88cb8 Thomas ANDREJAK
        for i in argv:
405
            for j in self.orderby:
406
                if str(i) == str(j):
407
                    break
408
            self.orderby.append(i)
409
410
    def format_events(self, first_row, last_row):
411
        """
412
        Formate la réponse de la requête et y applique les plugins
413
        pour un affichage simple du résultat par Genshi.
414
        On génère une liste de liste, chaqu'une étant la description de
415 a2a22ade Francois POIROTTE
        l'affichage pour un événement donné.
416 19e88cb8 Thomas ANDREJAK

417 a2a22ade Francois POIROTTE
        @param first_row: Indice de début de la liste des événements
418
        @param last_row: Indice de fin de la liste des événements
419 19e88cb8 Thomas ANDREJAK
        """
420 65383903 Francois POIROTTE
421 19e88cb8 Thomas ANDREJAK
        # Si la requête n'est pas générée, on le fait
422 911069bc Francois POIROTTE
        self.generate_request()
423 19e88cb8 Thomas ANDREJAK
424
        # Liste des éléments pour la tête du tableau
425 15b98053 Francois POIROTTE
        self.events = []
426 19e88cb8 Thomas ANDREJAK
427 15b98053 Francois POIROTTE
        for data in self.req[first_row : last_row]:
428 94f31908 Francois POIROTTE
            self.events.append(data)
429 19e88cb8 Thomas ANDREJAK
430 8484b8bd Francois POIROTTE
    def format_history(self):
431 19e88cb8 Thomas ANDREJAK
        """
432 a2a22ade Francois POIROTTE
        Formate les historiques correspondant aux événements sélectionnés
433 19e88cb8 Thomas ANDREJAK
        pour un affichage simple du résultat par Genshi.
434 8484b8bd Francois POIROTTE
        On génère une liste de liste, chaqu'une étant la description
435
        de l'affichage pour un historique donné.
436 19e88cb8 Thomas ANDREJAK
        """
437
438 539f69fc Francois POIROTTE
        ids = [data[0].idevent for data in self.events]
439 ee3ae8c8 Francois POIROTTE
        history = DBSession.query(
440
                    EventHistory,
441 15b98053 Francois POIROTTE
                ).filter(EventHistory.idevent.in_(ids)
442 19e88cb8 Thomas ANDREJAK
                ).order_by(desc(EventHistory.timestamp)
443
                ).order_by(desc(EventHistory.idhistory))
444 539f69fc Francois POIROTTE
        return history
445 19e88cb8 Thomas ANDREJAK
446 24d4f8b0 Vincent QUEMENER
    def generate_tmpl_context(self):
447 19e88cb8 Thomas ANDREJAK
        """
448
        Génère et peuple la variable tmpl_context avec les Dialogs et
449
        formulaires nécessaire au fonctionnement de Vigiboard
450
        """
451
452 97f6d842 Vincent QUEMENER
        from vigiboard.controllers.root import get_last_modification_timestamp
453 bcf87133 Francois POIROTTE
454
        # Si les objets manipulés sont des Event, on a facilement les idevent.
455 94f31908 Francois POIROTTE
        if not len(self.events):
456
            ids = []
457
        elif isinstance(self.events[0][0], Event):
458 bcf87133 Francois POIROTTE
            ids = [data[0].idevent for data in self.events]
459
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
460
        else:
461
            ids = [data[0].idcause for data in self.events]
462 e9ccb711 Francois POIROTTE
463 57387640 Francois POIROTTE
        # Ajout des formulaires et préparation
464
        # des données pour ces formulaires.
465
        tmpl_context.last_modification = \
466
            mktime(get_last_modification_timestamp(ids).timetuple())
467
468 e181e86c Francois POIROTTE
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
469 a5f99051 Francois POIROTTE
            submit_text=_('Apply'), action=url('/update'))