Diff of /qiita_db/portal.py [000000] .. [879b32]

Switch to unified view

a b/qiita_db/portal.py
1
# -----------------------------------------------------------------------------
2
# Copyright (c) 2014--, The Qiita Development Team.
3
#
4
# Distributed under the terms of the BSD 3-clause License.
5
#
6
# The full license is in the file LICENSE, distributed with this software.
7
# -----------------------------------------------------------------------------
8
import warnings
9
10
import qiita_db as qdb
11
12
13
class Portal(qdb.base.QiitaObject):
14
    r"""Portal object to create and maintain portals in the system
15
16
    Attributes
17
    ----------
18
    portal
19
20
    Methods
21
    -------
22
    get_studies
23
    add_studies
24
    remove_studies
25
    get_analyses
26
    add_analyses
27
    remove_analyses
28
    """
29
    _table = 'portal_type'
30
31
    def __init__(self, portal):
32
        with qdb.sql_connection.TRN:
33
            self.portal = portal
34
            portal_id = qdb.util.convert_to_id(portal, 'portal_type', 'portal')
35
            super(Portal, self).__init__(portal_id)
36
37
    @staticmethod
38
    def list_portals():
39
        """Returns list of non-default portals available in system
40
41
        Returns
42
        -------
43
        list of str
44
            List of portal names for the system
45
46
        Notes
47
        -----
48
        This does not return the QIITA portal in the list, as it is a required
49
        portal that can not be edited.
50
        """
51
        with qdb.sql_connection.TRN:
52
            sql = """SELECT portal
53
                     FROM qiita.portal_type
54
                     WHERE portal != 'QIITA'
55
                     ORDER BY portal"""
56
            qdb.sql_connection.TRN.add(sql)
57
            return qdb.sql_connection.TRN.execute_fetchflatten()
58
59
    @classmethod
60
    def create(cls, portal, desc):
61
        """Creates a new portal and its default analyses on the system
62
63
        Parameters
64
        ----------
65
        portal : str
66
            The name of the portal to add
67
        desc : str
68
            Description of the portal
69
70
        Raises
71
        ------
72
        QiitaDBDuplicateError
73
            Portal name already exists
74
        """
75
        if cls.exists(portal):
76
            raise qdb.exceptions.QiitaDBDuplicateError("Portal", portal)
77
78
        # Add portal and default analyses for all users
79
        sql = """DO $do$
80
            DECLARE
81
                pid bigint;
82
                eml varchar;
83
                aid bigint;
84
            BEGIN
85
                INSERT INTO qiita.portal_type (portal, portal_description)
86
                VALUES (%s, %s)
87
                RETURNING portal_type_id INTO pid;
88
89
                FOR eml IN
90
                    SELECT email FROM qiita.qiita_user
91
                LOOP
92
                    INSERT INTO qiita.analysis
93
                        (email, name, description, dflt)
94
                    VALUES (eml, eml || '-dflt', 'dflt', true)
95
                    RETURNING analysis_id INTO aid;
96
97
                    INSERT INTO qiita.analysis_portal
98
                        (analysis_id, portal_type_id)
99
                    VALUES (aid, pid);
100
                END LOOP;
101
            END $do$;"""
102
        qdb.sql_connection.perform_as_transaction(sql, [portal, desc])
103
104
        return cls(portal)
105
106
    @staticmethod
107
    def delete(portal):
108
        """Removes a portal and its default analyses from the system
109
110
        Parameters
111
        ----------
112
        portal : str
113
            The name of the portal to add
114
115
        Raises
116
        ------
117
        QiitaDBError
118
            Portal has analyses or studies attached to it
119
        """
120
        with qdb.sql_connection.TRN:
121
            # Check if attached to any studies
122
            portal_id = qdb.util.convert_to_id(portal, 'portal_type', 'portal')
123
            sql = """SELECT study_id
124
                     FROM qiita.study_portal
125
                     WHERE portal_type_id = %s"""
126
            qdb.sql_connection.TRN.add(sql, [portal_id])
127
            studies = qdb.sql_connection.TRN.execute_fetchflatten()
128
            if studies:
129
                raise qdb.exceptions.QiitaDBError(
130
                    " Cannot delete portal '%s', studies still attached: %s" %
131
                    (portal, ', '.join(map(str, studies))))
132
133
            # Check if attached to any analyses
134
            sql = """SELECT analysis_id
135
                     FROM qiita.analysis_portal
136
                        JOIN qiita.analysis USING (analysis_id)
137
                     WHERE portal_type_id = %s AND dflt = FALSE"""
138
            qdb.sql_connection.TRN.add(sql, [portal_id])
139
            analyses = qdb.sql_connection.TRN.execute_fetchflatten()
140
            if analyses:
141
                raise qdb.exceptions.QiitaDBError(
142
                    " Cannot delete portal '%s', analyses still attached: %s" %
143
                    (portal, ', '.join(map(str, analyses))))
144
145
            # Remove portal and default analyses for all users
146
            sql = """DO $do$
147
                DECLARE
148
                    aid bigint;
149
                BEGIN
150
                    FOR aid IN
151
                        SELECT analysis_id
152
                        FROM qiita.analysis_portal
153
                            JOIN qiita.analysis USING (analysis_id)
154
                        WHERE portal_type_id = %s AND dflt = True
155
                    LOOP
156
                        DELETE FROM qiita.analysis_portal
157
                        WHERE analysis_id = aid;
158
159
                        DELETE FROM qiita.analysis_sample
160
                        WHERE analysis_id = aid;
161
162
                        DELETE FROM qiita.analysis
163
                        WHERE analysis_id = aid;
164
                    END LOOP;
165
                    DELETE FROM qiita.portal_type WHERE portal_type_id = %s;
166
                END $do$;"""
167
            qdb.sql_connection.TRN.add(sql, [portal_id] * 2)
168
            qdb.sql_connection.TRN.execute()
169
170
    @staticmethod
171
    def exists(portal):
172
        """Returns whether the portal name already exists on the system
173
174
        Parameters
175
        ----------
176
        portal : str
177
            Name of portal to check
178
179
        Returns
180
        -------
181
        bool
182
            Whether the portal exists or not
183
        """
184
        try:
185
            qdb.util.convert_to_id(portal, 'portal_type', 'portal')
186
        except qdb.exceptions.QiitaDBLookupError:
187
            return False
188
        else:
189
            return True
190
191
    def get_studies(self):
192
        """Returns all studies belonging to the portal
193
194
        Returns
195
        -------
196
        set of qiita_db.study.Study
197
            All studies attached to the portal
198
        """
199
        with qdb.sql_connection.TRN:
200
            sql = """SELECT study_id
201
                     FROM qiita.study_portal
202
                     WHERE portal_type_id = %s"""
203
            qdb.sql_connection.TRN.add(sql, [self._id])
204
            return set(
205
                qdb.study.Study(sid)
206
                for sid in qdb.sql_connection.TRN.execute_fetchflatten())
207
208
    def _check_studies(self, studies):
209
        with qdb.sql_connection.TRN:
210
            # Check if any study IDs given do not exist.
211
            sql = "SELECT study_id FROM qiita.study WHERE study_id IN %s"
212
            qdb.sql_connection.TRN.add(sql, [tuple(studies)])
213
            existing = qdb.sql_connection.TRN.execute_fetchflatten()
214
            if len(existing) != len(list(studies)):
215
                bad = map(str, set(studies).difference(existing))
216
                raise qdb.exceptions.QiitaDBError(
217
                    "The following studies do not exist: %s" % ", ".join(bad))
218
219
    def add_studies(self, studies):
220
        """Adds studies to given portal
221
222
        Parameters
223
        ----------
224
        studies : iterable of int
225
            Study ids to attach to portal
226
227
        Raises
228
        ------
229
        QiitaDBError
230
            Some studies given do not exist in the system
231
        QiitaDBWarning
232
            Some studies already exist in the given portal
233
        """
234
        with qdb.sql_connection.TRN:
235
            self._check_studies(studies)
236
237
            # Clean list of studies down to ones not associated
238
            # with portal already
239
            sql = """SELECT study_id
240
                     FROM qiita.study_portal
241
                     WHERE portal_type_id = %s AND study_id IN %s"""
242
            qdb.sql_connection.TRN.add(sql, [self._id, tuple(studies)])
243
            duplicates = qdb.sql_connection.TRN.execute_fetchflatten()
244
245
            if len(duplicates) > 0:
246
                warnings.warn(
247
                    "The following studies are already part of %s: %s"
248
                    % (self.portal, ', '.join(map(str, duplicates))),
249
                    qdb.exceptions.QiitaDBWarning)
250
251
            # Add cleaned list to the portal
252
            clean_studies = set(studies).difference(duplicates)
253
            sql = """INSERT INTO qiita.study_portal (study_id, portal_type_id)
254
                     VALUES (%s, %s)"""
255
            if len(clean_studies) != 0:
256
                qdb.sql_connection.TRN.add(
257
                    sql, [[s, self._id] for s in clean_studies], many=True)
258
            qdb.sql_connection.TRN.execute()
259
260
    def remove_studies(self, studies):
261
        """Removes studies from given portal
262
263
        Parameters
264
        ----------
265
        studies : iterable of int
266
            Study ids to remove from portal
267
268
        Raises
269
        ------
270
        ValueError
271
            Trying to delete from QIITA portal
272
        QiitaDBError
273
            Some studies given do not exist in the system
274
            Some studies are already used in an analysis on the portal
275
        QiitaDBWarning
276
            Some studies already do not exist in the given portal
277
        """
278
        if self.portal == "QIITA":
279
            raise ValueError('Can not remove from main QIITA portal!')
280
281
        with qdb.sql_connection.TRN:
282
            self._check_studies(studies)
283
284
            # Make sure study not used in analysis in portal
285
            sql = """SELECT DISTINCT study_id
286
                     FROM qiita.study_artifact
287
                        JOIN qiita.analysis_sample USING (artifact_id)
288
                        JOIN qiita.analysis_portal USING (analysis_id)
289
                     WHERE portal_type_id = %s AND study_id IN %s"""
290
            qdb.sql_connection.TRN.add(sql, [self.id, tuple(studies)])
291
            analysed = qdb.sql_connection.TRN.execute_fetchflatten()
292
            if analysed:
293
                raise qdb.exceptions.QiitaDBError(
294
                    "The following studies are used in an analysis on portal "
295
                    "%s and can't be removed: %s"
296
                    % (self.portal, ", ".join(map(str, analysed))))
297
298
            # Clean list of studies down to ones associated with portal already
299
            sql = """SELECT study_id
300
                     FROM qiita.study_portal
301
                     WHERE portal_type_id = %s AND study_id IN %s"""
302
            qdb.sql_connection.TRN.add(sql, [self._id, tuple(studies)])
303
            clean_studies = qdb.sql_connection.TRN.execute_fetchflatten()
304
305
            if len(clean_studies) != len(studies):
306
                rem = map(str, set(studies).difference(clean_studies))
307
                warnings.warn(
308
                    "The following studies are not part of %s: %s"
309
                    % (self.portal, ', '.join(rem)),
310
                    qdb.exceptions.QiitaDBWarning)
311
312
            sql = """DELETE FROM qiita.study_portal
313
                     WHERE study_id IN %s AND portal_type_id = %s"""
314
            if len(clean_studies) != 0:
315
                qdb.sql_connection.TRN.add(sql, [tuple(studies), self._id])
316
            qdb.sql_connection.TRN.execute()
317
318
    def get_analyses(self):
319
        """Returns all analyses belonging to a portal
320
321
        Returns
322
        -------
323
        set of qiita_db.analysis.Analysis
324
            All analyses belonging to the portal
325
        """
326
        with qdb.sql_connection.TRN:
327
            sql = """SELECT analysis_id
328
                     FROM qiita.analysis_portal
329
                     WHERE portal_type_id = %s"""
330
            qdb.sql_connection.TRN.add(sql, [self._id])
331
            return set(
332
                qdb.analysis.Analysis(aid)
333
                for aid in qdb.sql_connection.TRN.execute_fetchflatten())
334
335
    def _check_analyses(self, analyses):
336
        with qdb.sql_connection.TRN:
337
            # Check if any analysis IDs given do not exist.
338
            sql = """SELECT analysis_id
339
                     FROM qiita.analysis
340
                     WHERE analysis_id IN %s"""
341
            qdb.sql_connection.TRN.add(sql, [tuple(analyses)])
342
            existing = qdb.sql_connection.TRN.execute_fetchflatten()
343
            if len(existing) != len(analyses):
344
                bad = map(str, set(analyses).difference(existing))
345
                raise qdb.exceptions.QiitaDBError(
346
                    "The following analyses do not exist: %s" % ", ".join(bad))
347
348
            # Check if any analyses given are default
349
            sql = """SELECT analysis_id
350
                     FROM qiita.analysis
351
                     WHERE analysis_id IN %s AND dflt = True"""
352
            qdb.sql_connection.TRN.add(sql, [tuple(analyses)])
353
            default = qdb.sql_connection.TRN.execute_fetchflatten()
354
            if len(default) > 0:
355
                bad = map(str, set(analyses).difference(default))
356
                raise qdb.exceptions.QiitaDBError(
357
                    "The following analyses are default and can't be deleted "
358
                    "or assigned to another portal: %s" % ", ".join(bad))
359
360
    def add_analyses(self, analyses):
361
        """Adds analyses to given portal
362
363
        Parameters
364
        ----------
365
        analyses : iterable of int
366
            Analysis ids to attach to portal
367
368
        Raises
369
        ------
370
        QiitaDBError
371
            Some given analyses do not exist in the system,
372
            or are default analyses
373
            Portal does not contain all studies used in analyses
374
        QiitaDBWarning
375
            Some analyses already exist in the given portal
376
        """
377
        with qdb.sql_connection.TRN:
378
            self._check_analyses(analyses)
379
380
            if self.portal != "QIITA":
381
                # Make sure new portal has access to all studies in analysis
382
                sql = """SELECT DISTINCT analysis_id
383
                         FROM qiita.analysis_sample
384
                            JOIN qiita.study_artifact
385
                                USING (artifact_id)
386
                         WHERE study_id NOT IN (
387
                            SELECT study_id
388
                            FROM qiita.study_portal
389
                            WHERE portal_type_id = %s)
390
                         AND analysis_id IN %s
391
                         ORDER BY analysis_id"""
392
                qdb.sql_connection.TRN.add(sql, [self._id, tuple(analyses)])
393
                missing_info = qdb.sql_connection.TRN.execute_fetchflatten()
394
                if missing_info:
395
                    raise qdb.exceptions.QiitaDBError(
396
                        "Portal %s is mising studies used in the following "
397
                        "analyses: %s"
398
                        % (self.portal, ", ".join(map(str, missing_info))))
399
400
            # Clean list of analyses to ones not already associated with portal
401
            sql = """SELECT analysis_id
402
                     FROM qiita.analysis_portal
403
                        JOIN qiita.analysis USING (analysis_id)
404
                     WHERE portal_type_id = %s AND analysis_id IN %s
405
                        AND dflt != TRUE"""
406
            qdb.sql_connection.TRN.add(sql, [self._id, tuple(analyses)])
407
            duplicates = qdb.sql_connection.TRN.execute_fetchflatten()
408
409
            if len(duplicates) > 0:
410
                warnings.warn(
411
                    "The following analyses are already part of %s: %s"
412
                    % (self.portal, ', '.join(map(str, duplicates))),
413
                    qdb.exceptions.QiitaDBWarning)
414
415
            sql = """INSERT INTO qiita.analysis_portal
416
                        (analysis_id, portal_type_id)
417
                     VALUES (%s, %s)"""
418
            clean_analyses = set(analyses).difference(duplicates)
419
            if len(clean_analyses) != 0:
420
                qdb.sql_connection.TRN.add(
421
                    sql, [[a, self._id] for a in clean_analyses], many=True)
422
            qdb.sql_connection.TRN.execute()
423
424
    def remove_analyses(self, analyses):
425
        """Removes analyses from given portal
426
427
        Parameters
428
        ----------
429
        analyses : iterable of int
430
            Analysis ids to remove from portal
431
432
        Raises
433
        ------
434
        ValueError
435
            Trying to delete from QIITA portal
436
        QiitaDBWarning
437
            Some analyses already do not exist in the given portal
438
        """
439
        with qdb.sql_connection.TRN:
440
            self._check_analyses(analyses)
441
            if self.portal == "QIITA":
442
                raise ValueError('Can not remove from main QIITA portal!')
443
444
            # Clean list of analyses to ones already associated with portal
445
            sql = """SELECT analysis_id
446
                     FROM qiita.analysis_portal
447
                        JOIN qiita.analysis USING (analysis_id)
448
                     WHERE portal_type_id = %s AND analysis_id IN %s
449
                        AND dflt != TRUE"""
450
            qdb.sql_connection.TRN.add(sql, [self._id, tuple(analyses)])
451
            clean_analyses = qdb.sql_connection.TRN.execute_fetchflatten()
452
453
            if len(clean_analyses) != len(analyses):
454
                rem = map(str, set(analyses).difference(clean_analyses))
455
                warnings.warn(
456
                    "The following analyses are not part of %s: %s"
457
                    % (self.portal, ', '.join(rem)),
458
                    qdb.exceptions.QiitaDBWarning)
459
460
            sql = """DELETE FROM qiita.analysis_portal
461
                     WHERE analysis_id IN %s AND portal_type_id = %s"""
462
            if len(clean_analyses) != 0:
463
                qdb.sql_connection.TRN.add(
464
                    sql, [tuple(clean_analyses), self._id])
465
            qdb.sql_connection.TRN.execute()