RSS

(root)/packagedb/0.5.x : /pkgdb/listqueries.py (revision 650)

To get this branch, use:
bzr branch /bzr/packagedb/0.5.x
Line Revision Contents
1 111
# -*- coding: utf-8 -*-
2 168
#
3 644
# Copyright © 2007-2012  Red Hat, Inc.
4 168
#
5
# This copyrighted material is made available to anyone wishing to use, modify,
6
# copy, or redistribute it subject to the terms and conditions of the GNU
7 537.10.6
# General Public License v.2, or (at your option) any later version.  This
8
# program is distributed in the hope that it will be useful, but WITHOUT ANY
9
# WARRANTY expressed or implied, including the implied warranties of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11
# Public License for more details.  You should have received a copy of the GNU
12
# General Public License along with this program; if not, write to the Free
13
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
14
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the source
15
# code or documentation are not subject to the GNU General Public License and
16
# may only be used or replicated with the express permission of Red Hat, Inc.
17 168
#
18
# Red Hat Author(s): Toshio Kuratomi <tkuratom@redhat.com>
19
#
20
'''
21
Send acl information to third party tools.
22
'''
23 512
#
24
#pylint Explanations
25
#
26
27
# :E1101: SQLAlchemy monkey patches database fields into the mapper classes so
28
#   we have to disable this when accessing an attribute of a mapped class.
29
# :W0232: no __init__ method: This only applies to a validator schema.  Those
30
#   don't have methods, just attributes so it's expected.
31
# :R0903: Too few public methods: This only applies to the validator schema
32
#   and two classes that we're using as data structures that can return json.
33
#   So this is fine.
34 527
# :C0322: Disable space around operator checking in multiline decorators
35 168
36 537.1.10
import os
37
import tempfile
38 48.8.1
import itertools
39 447
40 537.1.18
from sqlalchemy import select, and_, create_engine, union
41 520
from sqlalchemy.orm import sessionmaker
42 447
43 329.1.23
from turbogears import expose, validate, error_handler
44
from turbogears import controllers, validators
45
46 537.1.32
from turbogears.database import get_engine, session
47 447
48 537.1.18
49 528
from pkgdb.model import Package, Branch, GroupPackageListing, Collection, \
50 537.1.18
        GroupPackageListingAcl, PackageListing, PersonPackageListing, \
51 537.1.85
        PersonPackageListingAcl, Repo, PackageBuild, PackageBuildRepo, Tag
52 537.1.33
from pkgdb.model import PackageTable, PackageListingTable, \
53
        PersonPackageListingTable, PersonPackageListingAclTable, \
54
        CollectionTable, ApplicationTag, PackageBuildApplicationsTable, \
55 537.1.71
        BinaryPackageTag, BranchTable
56 537.1.18
from pkgdb.model import YumTagsTable
57 537.1.9
from pkgdb.model.yumdb import yummeta
58 537.10.5
from pkgdb.lib.utils import STATUS
59 48.2.240
from pkgdb import _
60 102
61 537.1.71
from pkgdb.lib.validators import CollectionNameVersion, SetOf, \
62
        IsCollectionSimpleNameRegex
63 329.1.23
64 638
from fedora.tg.utils import jsonify_validation_errors
65 329.1.23
66 459
67 329.1.23
#
68
# Validators
69
#
70
71
class NotifyList(validators.Schema):
72 340
    '''Validator schema for the notify method.'''
73
    # validator schemas don't have methods (R0903, W0232)
74 512
    #pylint:disable-msg=R0903,W0232
75 340
76 329.1.23
    # We don't use a more specific validator for collection or version because
77
    # the chained validator does it for us and we don't want to hit the
78
    # database multiple times
79
    name = validators.UnicodeString(not_empty=False, strip=True)
80
    version = validators.UnicodeString(not_empty=False, strip=True)
81 537.6.1
    eol = validators.StringBool()
82 329.1.23
    chained_validators = (CollectionNameVersion(),)
83
84
#
85
# Supporting Objects
86
#
87
88 102
class AclList(object):
89 274.1.2
    '''List of people and groups who hold this acl.
90
    '''
91 340
    # This class is just a data structure that can convert itself to json so
92
    # there's no need for a lot of methods.
93 512
    #pylint:disable-msg=R0903
94 340
95 274.1.2
    ### FIXME: Reevaluate whether we need this data structure at all.  Once
96
    # jsonified, it is transformed into a dict of lists so it might not be
97
    # good to do it this way.
98 102
    def __init__(self, people=None, groups=None):
99
        self.people = people or []
100
        self.groups = groups or []
101 104
102 102
    def __json__(self):
103
        return {'people' : self.people,
104
                'groups' : self.groups
105
                }
106
107 104
class BugzillaInfo(object):
108 274.1.2
    '''Information necessary to construct a bugzilla record for a package.
109
    '''
110 340
    # This class is just a data structure that can convert itself to json so
111
    # there's no need for a lot of methods.
112 512
    #pylint:disable-msg=R0903
113 340
114
    ### FIXME: Reevaluate whether we need this data structure at all.  Once
115
    # jsonified, it is transformed into a dict of lists so it might not be
116
    # good to do it this way.
117 104
    def __init__(self, owner=None, summary=None, cclist=None, qacontact=None):
118
        self.owner = owner
119
        self.summary = summary
120
        self.cclist = cclist or AclList()
121
        self.qacontact = qacontact
122
123
    def __json__(self):
124
        return {'owner' : self.owner,
125
                'summary' : self.summary,
126
                'cclist' : self.cclist,
127
                'qacontact' : self.qacontact
128
                }
129
130 329.1.23
#
131
# Controllers
132
#
133
134 339
class ListQueries(controllers.Controller):
135 274.1.1
    '''Controller for lists of acl/owner information needed by external tools.
136
137
    Although these methods can return web pages, the main feature is the json
138
    and plain text that they return as the main usage of this is for external
139
    tools to take data for their use.
140
    '''
141 408.1.102
    def __init__(self, app_title=None):
142 365
        self.app_title = app_title
143 102
144 340
    def _add_to_bugzilla_acl_list(self, package_acls, pkg_name,
145
            collection_name, identity, group=None):
146 274.1.2
        '''Add the given acl to the list of acls for bugzilla.
147
148 48.2.240
        :arg package_acls: Data structure to fill
149
        :arg pkg_name: Name of the package we're setting the acl on
150
        :arg collection_name: Name of the bugzilla collection on which we're
151 274.1.2
            setting the acl.
152 48.2.240
        :arg identity: Id of the user or group for whom the acl is being set.
153
        :kwarg group: If set, we're dealing with a group instead of a person.
154 274.1.2
        '''
155 104
        # Lookup the collection
156
        try:
157 340
            collection = package_acls[collection_name]
158 104
        except KeyError:
159
            collection = {}
160 340
            package_acls[collection_name] = collection
161 104
        # Then the package
162
        try:
163 340
            package = collection[pkg_name]
164 104
        except KeyError:
165
            package = BugzillaInfo()
166 340
            collection[pkg_name] = package
167 104
        # Then add the acl
168
        if group:
169
            try:
170 106
                package.cclist.groups.append(identity)
171 274.1.1
            except KeyError:
172 106
                package.cclist = AclList(groups=[identity])
173 104
        else:
174
            try:
175 106
                package.cclist.people.append(identity)
176 274.1.2
            except KeyError:
177 106
                package.cclist = AclList(people=[identity])
178 104
179 340
    def _add_to_vcs_acl_list(self, package_acls, acl, pkg_name, branch_name,
180 104
            identity, group=None):
181 274.1.2
        '''Add the given acl to the list of acls for the vcs.
182
183 48.2.240
        :arg package_acls: Data structure to fill
184
        :arg acl: Acl to create
185
        :arg pkg_name: Name of the package we're setting the acl on
186
        :arg branch_name: Name of the branch for which the acl is being set
187
        :arg identity: id of the user or group for whom the acl is being set.
188
        :kwarg group: If set, we're dealing with a group instead of a person.
189 274.1.2
        '''
190 102
        # Key by package name
191
        try:
192 340
            pkg = package_acls[pkg_name]
193 102
        except KeyError:
194
            pkg = {}
195 340
            package_acls[pkg_name] = pkg
196 102
197
        # Then by branch name
198
        try:
199 340
            branch = pkg[branch_name]
200 102
        except KeyError:
201
            branch = {}
202 340
            pkg[branch_name] = branch
203 102
204
        # Add these acls to the group acls
205 104
        if group:
206
            try:
207
                branch[acl].groups.append(identity)
208 274.1.1
            except KeyError:
209 104
                branch[acl] = AclList(groups=[identity])
210
        else:
211
            try:
212
                branch[acl].people.append(identity)
213 274.1.1
            except KeyError:
214 104
                branch[acl] = AclList(people=[identity])
215 102
216 537.1.121
    @expose(template='mako:/plain/vcsacls.mak',
217 525
            as_format='plain', accept_format='text/plain',
218
            content_type='text/plain; charset=utf-8', #pylint:disable-msg=C0322
219
            format='text') #pylint:disable-msg=C0322
220
    @expose(template='pkgdb.templates.vcsacls', allow_json=True)
221 102
    def vcs(self):
222
        '''Return ACLs for the version control system.
223
224
        The format of the returned data is this:
225
        packageAcls['pkg']['branch']['acl'].'type' = (list of users/groups)
226
        For instance:
227
          packageAcls['bzr']['FC-6']['commit'].group = (cvsextras,)
228
          packageAcls['bzr']['FC-6']['commit'].people = (shahms, toshio)
229
230
        This method can display a long list of users but most people will want
231
        to access it as JSON data with the ?tg_format=json query parameter.
232
        '''
233
        # Store our acls in a dict
234 340
        package_acls = {}
235 102
236
        # Get the vcs group acls from the db
237 303
238 340
        group_acls = select((
239 516
            #pylint:disable-msg=E1101
240 303
            Package.name,
241 315
            Branch.branchname,
242 287.4.6
            GroupPackageListing.groupname), and_(
243 303
                GroupPackageListingAcl.acl == 'commit',
244 537.1.129
                GroupPackageListingAcl.statuscode == STATUS['Approved'],
245 303
                GroupPackageListingAcl.grouppackagelistingid \
246
                        == GroupPackageListing.id,
247
                GroupPackageListing.packagelistingid \
248
                        == PackageListing.id,
249
                PackageListing.packageid == Package.id,
250
                PackageListing.collectionid == Collection.id,
251
                Branch.collectionid == Collection.id,
252 537.1.129
                PackageListing.statuscode != STATUS['Removed'],
253
                Package.statuscode != STATUS['Removed']
254 102
                )
255
            )
256 274.1.1
257 315
        groups = {}
258
259 102
        # Save them into a python data structure
260 340
        for record in group_acls.execute():
261 315
            if not record[2] in groups:
262 48.2.177
                groups[record[2]] = record[2]
263 340
            self._add_to_vcs_acl_list(package_acls, 'commit',
264 111
                    record[0], record[1],
265 315
                    groups[record[2]], group=True)
266 340
        del group_acls
267 102
268
        # Get the package owners from the db
269 156
        # Exclude the orphan user from that.
270 340
        owner_acls = select((
271 516
            #pylint:disable-msg=E1101
272 303
            Package.name,
273
            Branch.branchname, PackageListing.owner),
274 302
            and_(
275 303
                PackageListing.packageid==Package.id,
276
                PackageListing.collectionid==Collection.id,
277 287.4.4
                PackageListing.owner!='orphan',
278 303
                Collection.id==Branch.collectionid,
279 537.1.129
                PackageListing.statuscode != STATUS['Removed'],
280
                Package.statuscode != STATUS['Removed']
281 102
                ),
282 303
            order_by=(PackageListing.owner,)
283 102
            )
284
285
        # Save them into a python data structure
286 340
        for record in owner_acls.execute():
287 48.2.177
            username = record[2]
288 340
            self._add_to_vcs_acl_list(package_acls, 'commit',
289 111
                    record[0], record[1],
290
                    username, group=False)
291 340
        del owner_acls
292 102
293
        # Get the vcs user acls from the db
294 340
        person_acls = select((
295 516
            #pylint:disable-msg=E1101
296 303
            Package.name,
297 287.4.4
            Branch.branchname, PersonPackageListing.username),
298 302
            and_(
299 303
                PersonPackageListingAcl.acl=='commit',
300 537.1.129
                PersonPackageListingAcl.statuscode == STATUS['Approved'],
301 303
                PersonPackageListingAcl.personpackagelistingid \
302
                        == PersonPackageListing.id,
303
                PersonPackageListing.packagelistingid \
304
                        == PackageListing.id,
305
                PackageListing.packageid == Package.id,
306
                PackageListing.collectionid == Collection.id,
307
                Branch.collectionid == Collection.id,
308 537.1.129
                PackageListing.statuscode != STATUS['Removed'],
309
                Package.statuscode != STATUS['Removed']
310 102
                ),
311 287.4.4
            order_by=(PersonPackageListing.username,)
312 102
            )
313
        # Save them into a python data structure
314 340
        for record in person_acls.execute():
315 48.2.177
            username = record[2]
316 340
            self._add_to_vcs_acl_list(package_acls, 'commit',
317 111
                    record[0], record[1],
318
                    username, group=False)
319 102
320 48.2.240
        return dict(title=_('%(app)s -- VCS ACLs') % {'app': self.app_title},
321 340
                packageAcls=package_acls)
322 104
323 569
    @expose(template='mako:plain.buildtags',
324
            as_format="plain", accept_format="text/plain",
325
            content_type="text/plain; charset=utf-8",
326
            format='text')
327
    @expose(template='pkgdb.templates.xml.buildtags', as_format='xml',
328
            accept_format='application/xml')
329
    @expose(template="pkgdb.templates.buildtags", allow_json=True)
330 529.2.2
    def buildtags(self, repos):
331 572
        '''Return a dictionary with all the PackageBuild tags and their scores.
332 472.1.3
        The PackageBuild tags are tags binded to applications belonging to 
333
        the packagebuild. When there are more apps with same tag within one 
334
        packaebuild, the maximum score is taken.
335 443
336
        :arg repoName: A repo shortname to lookup packagebuilds into
337
        for tags
338
339
        Returns:
340
        :buildtags: a dictionary of buildaname : tagdict, where tagdict is a
341 472.1.3
        dictionary of tag : score key-value pairs. 
342 443
        '''
343 537.1.9
        if not isinstance(repos, list):
344 443
            repos = [repos]
345 516
346 443
        buildtags = {}
347
        for repo in repos:
348
            buildtags[repo] = {}
349 516
350
            #pylint:disable-msg=E1101
351 572
            repo_query = select((Repo.id,))
352
            repo_query = repo_query.where(Repo.shortname == repo)
353
            for result in repo_query.execute():
354
                repoid = result[0]
355
            #pylint:enable-msg=E1101
356
357
            pkgbld_query = select((PackageBuild.name,
358
                                   Tag.name,
359
                                   ApplicationTag.score),
360
                               and_(Tag.id == ApplicationTag.tagid,
361
                                    ApplicationTag.applicationid == PackageBuildApplicationsTable.c.applicationid,
362
                                    PackageBuildApplicationsTable.c.packagebuildid == PackageBuild.id,
363
                                    PackageBuildRepo.repoid == Repo.id,
364 644
                                    PackageBuildRepo.packagebuildid == \
365
                                        PackageBuild.id,
366 572
                                    Repo.shortname==repo))
367
            pkgblds = pkgbld_query.execute()
368
            #pylint:enable-msg=E1101
369
370
            for pkgbld in pkgblds:
371
                pkgname = pkgbld[0]
372
                tag = pkgbld[1]
373
                score = pkgbld[2]
374
                if (not buildtags[repo].has_key(pkgname)):
375
                    buildtags[repo][pkgname] = {}
376
                buildtags[repo][pkgname][tag] = score
377 569
378
        return dict(title=_('%(app)s -- Build Tags') % {'app': self.app_title},
379
                    buildtags=buildtags, repos=repos)
380 447
381
    @expose(content_type='application/sqlite')
382 537.1.18
    def sqlitebuildtags(self, repo):
383 447
        '''Return a sqlite database of packagebuilds and tags.
384
385
        The database returned will contain copies or subsets of tables in the
386
        pkgdb.
387
388 644
        :arg repo: A repository shortname to retrieve tags for
389
                   (e.g. 'F-11-i386')
390 447
391 537.1.93
        .. versionadded:: 0.5.0
392
393 447
        '''
394
        # initialize/clear database
395 537.1.9
        fd, dbfile = tempfile.mkstemp()
396
        os.close(fd)
397
        sqliteconn = 'sqlite:///%s' % dbfile
398
399 537.1.10
        yummeta.bind = create_engine(sqliteconn)
400 447
        yummeta.create_all()
401
402 452
        # since we're using two databases, we'll need a new session
403 447
        default_engine = get_engine()
404 537.1.10
        lite_session = sessionmaker(yummeta.bind)()
405 537.1.9
406 537.1.18
        # Retrieve the tags from the database
407
        tags = union(select(
408
            (PackageBuild.name.label('name'),
409
                Tag.name.label('tag'),
410
                ApplicationTag.score.label('score')),
411
            and_(Tag.id==ApplicationTag.tagid,
412 644
                 ApplicationTag.applicationid==\
413
                     PackageBuildApplicationsTable.c.applicationid,
414
                 PackageBuildApplicationsTable.c.packagebuildid==\
415
                     PackageBuild.id,
416
                 PackageBuildRepo.repoid==Repo.id,
417
                 PackageBuildRepo.packagebuildid==PackageBuild.id,
418
                 Repo.shortname==repo)),
419 537.1.18
            select((PackageBuild.name.label('name'),
420
                Tag.name.label('tag'),
421
                BinaryPackageTag.score.label('score')),
422
            and_(Tag.id==BinaryPackageTag.tagid,
423
                BinaryPackageTag.binarypackagename==PackageBuild.name,
424 537.1.85
                PackageBuildRepo.repoid==Repo.id,
425
                PackageBuildRepo.packagebuildid==PackageBuild.id,
426 537.1.88
                Repo.shortname==repo)))
427 537.1.18
428
        pkg_tags = []
429 537.1.86
        ### HACK: Should be able to do this in SQL somehow I think but it
430
        # requires merging the scores somehow
431
        used_tags = set()
432 537.1.18
        for tag in tags.execute().fetchall():
433 537.1.86
            if (tag[0], tag[1]) not in used_tags:
434 644
                pkg_tags.append({'name': tag[0], 'tag': tag[1],
435
                                 'score': tag[2]})
436 537.1.86
                used_tags.add((tag[0], tag[1]))
437 537.1.18
438 537.1.93
        if pkg_tags:
439
            # If there's no tags, we'll return an empty database
440
            lite_session.execute(YumTagsTable.insert(), pkg_tags)
441
442 447
        lite_session.commit()
443
444
        f = open(dbfile, 'r')
445
        dump = f.read()
446
        f.close()
447 537.1.9
        os.unlink(dbfile)
448 447
        return dump
449 537.1.9
450 537.1.123
    @expose(template="mako:/plain/bugzillaacls.mak",
451 157
            as_format="plain", accept_format="text/plain",
452 525
            content_type="text/plain; charset=utf-8", #pylint:disable-msg=C0322
453
            format='text') #pylint:disable-msg=C0322
454 104
    @expose(template="pkgdb.templates.bugzillaacls", allow_json=True)
455 644
    def bugzilla(self, collection=None):
456 104
        '''Return the package attributes used by bugzilla.
457
458 644
        :karg collection: Name of the bugzilla collection to gather data on.
459
460 104
        Note: The data returned by this function is for the way the current
461
        Fedora bugzilla is setup as of (2007/6/25).  In the future, bugzilla
462 479
        may change to have separate products for each collection-version.
463 104
        When that happens we'll have to change what this function returns.
464
465
        The returned data looks like this:
466
467
        bugzillaAcls[collection][package].attribute
468
        attribute is one of:
469 48.2.240
            :owner: FAS username for the owner
470
            :qacontact: if the package has a special qacontact, their userid
471
                is listed here
472
            :summary: Short description of the package
473
            :cclist: list of FAS userids that are watching the package
474 104
        '''
475 340
        bugzilla_acls = {}
476 104
        username = None
477
478 48.12.8
        # select all packages that are in an active release
479 644
        #pylint:disable-msg=E1101
480
        package_info = select((Collection.name, Package.name,
481
                               PackageListing.owner, PackageListing.qacontact,
482
                               Package.summary),
483
                              and_(Collection.id==PackageListing.collectionid,
484
                                   Package.id==PackageListing.packageid,
485
                                   Package.statuscode!=STATUS['Removed'],
486
                                   PackageListing.statuscode!=STATUS['Removed'],
487
                                   Collection.statuscode.in_((STATUS['Active'],
488
                                       STATUS['Under Development'])),
489
                                  ),
490
                              order_by=(Collection.name,), distinct=True)
491
        if (collection):
492
            package_info = package_info.where(Collection.branchname==collection);
493 104
494 48.2.91
        # List of packages that need more processing to decide who the owner
495
        # should be.
496 340
        undupe_owners = []
497 48.2.91
498 340
        for pkg in package_info.execute():
499 104
            # Lookup the collection
500 340
            collection_name = pkg[0]
501 644
            if (collection):
502
                collection_name += ' (%s)' % collection
503
504 104
            try:
505 645
                collections = bugzilla_acls[collection_name]
506 104
            except KeyError:
507 644
                collections = {}
508
                bugzilla_acls[collection_name] = collections
509 104
            # Then the package
510 340
            package_name = pkg[1]
511 104
            try:
512 644
                package = collections[package_name]
513 104
            except KeyError:
514
                package = BugzillaInfo()
515 644
                collections[package_name] = package
516 104
517
            # Save the package information in the data structure to return
518 48.2.91
            if not package.owner:
519 48.2.177
                package.owner = pkg[2]
520
            elif pkg[2] != package.owner:
521 48.2.91
                # There are multiple owners for this package.
522 340
                undupe_owners.append(package_name)
523 104
            if pkg[3]:
524 48.2.177
                package.qacontact = pkg[3]
525 104
            package.summary = pkg[4]
526 111
527 340
        if undupe_owners:
528 48.2.91
            # These are packages that have different owners in different
529
            # branches.  Need to find one to be the owner of the bugzilla
530
            # component
531 516
            #pylint:disable-msg=E1101
532 644
            package_info = select((Collection.name, Collection.version,
533
                                   Package.name, PackageListing.owner),
534
                                  and_(Collection.id==\
535
                                           PackageListing.collectionid,
536
                                       Package.id==\
537
                                           PackageListing.packageid,
538
                                       Package.statuscode!=STATUS['Removed'],
539
                                       PackageListing.statuscode!=\
540
                                           STATUS['Removed'],
541
                                       Collection.statuscode.\
542
                                           in_((STATUS['Active'],
543
                                                STATUS['Under Development'])),
544
                                       Package.name.in_(undupe_owners),
545
                                      ),
546
                                  order_by=(Collection.name,
547
                                            Collection.version),
548
                                  distinct=True)
549
550
            if (collection):
551
                package_info = package_info.where(Collection.branchname==\
552
                                                  collection);
553 516
            #pylint:enable-msg=E1101
554 48.2.91
555
            # Organize the results so that we have:
556
            # [packagename][collectionname][collectionversion] = owner
557 340
            by_pkg = {}
558
            for pkg in package_info.execute():
559 48.2.91
                # Order results by package
560
                try:
561 340
                    package = by_pkg[pkg[2]]
562 48.2.91
                except KeyError:
563
                    package = {}
564 340
                    by_pkg[pkg[2]] = package
565 48.2.91
566
                # Then collection
567 644
                if (collection):
568
                    pkg[0] += ' (%s)' % collection
569 48.2.91
                try:
570 647
                    collections = package[pkg[0]]
571 48.2.91
                except KeyError:
572 644
                    collections = {}
573
                    package[pkg[0]] = collections
574 48.2.91
575
                # Then collection version == owner
576 644
                collections[pkg[1]] = pkg[3]
577 48.2.91
578
            # Find the proper owner
579 340
            for pkg in by_pkg:
580 644
                for collection_name in by_pkg[pkg]:
581
                    if collection_name.startswith('Fedora'):
582 48.2.91
                        # If devel exists, use its owner
583
                        # We can safely ignore orphan because we already know
584
                        # this is a dupe and thus a non-orphan exists.
585 644
                        if 'devel' in by_pkg[pkg][collection_name]:
586
                            if by_pkg[pkg][collection_name]['devel'] == 'orphan'\
587
                                    and len(by_pkg[pkg][collection_name]) > 1:
588 48.2.91
                                # If there are other owners, try to use them
589
                                # instead of orphan
590 644
                                del by_pkg[pkg][collection_name]['devel']
591 48.2.91
                            else:
592
                                # Prefer devel above all others
593 644
                                bugzilla_acls[collection_name][pkg].owner = \
594
                                        by_pkg[pkg][collection_name]['devel']
595 48.2.91
                                continue
596
597
                    # For any collection except Fedora or Fedora if the devel
598
                    # version does not exist, treat releases as numbers and
599
                    # take the results from the latest number
600 644
                    releases = [int(r) for r in by_pkg[pkg][collection_name] \
601
                            if by_pkg[pkg][collection_name][r] != 'orphan']
602 48.2.91
                    if not releases:
603
                        # Every release was an orphan
604 644
                        bugzilla_acls[collection_name][pkg].owner = 'orphan'
605 48.2.91
                    else:
606
                        releases.sort()
607 644
                        bugzilla_acls[collection_name][pkg].owner = \
608
                            by_pkg[pkg][collection_name][unicode(releases[-1])]
609 48.2.91
610 106
        # Retrieve the user acls
611 274.1.1
612 644
        #pylint:disable-msg=E1101
613
        person_acls = select((Package.name, Collection.name,
614
                              PersonPackageListing.username),
615
                             and_(PersonPackageListingAcl.acl==\
616
                                      'watchbugzilla',
617
                                  PersonPackageListingAcl.statuscode==\
618
                                      STATUS['Approved'],
619
                                  PersonPackageListingAcl.\
620
                                      personpackagelistingid==\
621
                                      PersonPackageListing.id,
622
                                  PersonPackageListing.packagelistingid==\
623
                                      PackageListing.id,
624
                                  PackageListing.packageid==Package.id,
625
                                  PackageListing.collectionid==Collection.id,
626
                                  Package.statuscode!=STATUS['Removed'],
627
                                  PackageListing.statuscode!=STATUS['Removed'],
628
                                  Collection.statuscode.in_((STATUS['Active'],
629
                                      STATUS['Under Development'])),
630
                                 ),
631
                             order_by=(PersonPackageListing.username,),
632
                             distinct=True
633
                            )
634
        if (collection):
635
            person_acls = person_acls.where(Collection.branchname==collection);
636 329.1.23
637 106
        # Save them into a python data structure
638 340
        for record in person_acls.execute():
639 48.2.177
            username = record[2]
640 644
            collection_name = record[1]
641
            if (collection):
642
                collection_name += ' (%s)' % collection
643
            self._add_to_bugzilla_acl_list(bugzilla_acls, record[0], collection_name,
644 274.1.1
                    username, group=False)
645 106
646
        ### TODO: No group acls at the moment
647
        # There are no group acls to take advantage of this.
648 48.2.240
        return dict(title=_('%(app)s -- Bugzilla ACLs') % {
649
            'app': self.app_title}, bugzillaAcls=bugzilla_acls)
650 329.1.23
651
    @validate(validators=NotifyList())
652
    @error_handler()
653 537.1.119
    @expose(template='mako:/plain/notify.mak',
654 329.1.23
            as_format='plain', accept_format='text/plain',
655 525
            content_type='text/plain; charset=utf-8', #pylint:disable-msg=C0322
656
            format='text') #pylint:disable-msg=C0322
657 329.1.23
    @expose(template='pkgdb.templates.notify', allow_json=True)
658
    def notify(self, name=None, version=None, eol=False):
659
        '''List of usernames that should be notified of changes to a package.
660
661
        For the collections specified we want to retrieve all of the owners,
662
        watchbugzilla, and watchcommits accounts.
663
664 48.2.240
        :kwarg name: Set to a collection name to filter the results for that
665
        :kwarg version: Set to a collection version to further filter results
666
            for a single version
667
        :kwarg eol: Set to True if you want to include end of life
668
            distributions
669 329.1.23
        '''
670
        # Check for validation errors requesting this form
671
        errors = jsonify_validation_errors()
672
        if errors:
673
            return errors
674
675
        # Retrieve Packages, owners, and people on watch* acls
676 516
        #pylint:disable-msg=E1101
677 48.8.1
        owner_query = select((Package.name, PackageListing.owner),
678 537.3.2
                from_obj=(PackageTable.join(PackageListingTable).join(
679 48.8.1
                    CollectionTable))).where(and_(
680 537.1.129
                        Package.statuscode == STATUS['Approved'],
681
                        PackageListing.statuscode == STATUS['Approved'])
682 48.8.1
                        ).distinct().order_by('name')
683 48.2.177
        watcher_query = select((Package.name, PersonPackageListing.username),
684 537.1.33
                from_obj=(PackageTable.join(PackageListingTable).join(
685
                    CollectionTable).join(PersonPackageListingTable).join(
686
                        PersonPackageListingAclTable))).where(and_(
687 537.1.129
                            Package.statuscode == STATUS['Approved'],
688
                            PackageListing.statuscode == STATUS['Approved'],
689 48.8.1
                            PersonPackageListingAcl.acl.in_(
690
                                ('watchbugzilla', 'watchcommits')),
691
                            PersonPackageListingAcl.statuscode ==
692 537.1.129
                                STATUS['Approved']
693 355
                        )).distinct().order_by('name')
694 516
        #pylint:enable-msg=E1101
695 340
696 329.1.23
        if not eol:
697
            # Filter out eol distributions
698 516
            #pylint:disable-msg=E1101
699 537.1.120
            owner_query = owner_query.where(CollectionTable.c.statuscode.in_(
700 537.1.129
                (STATUS['Active'], STATUS['Under Development'])))
701 644
            watcher_query = watcher_query.where(CollectionTable.c.statuscode.\
702
                                          in_((STATUS['Active'],
703
                                               STATUS['Under Development'])))
704 516
            #pylint:enable-msg=E1101
705 329.1.23
706
        # Only grab from certain collections
707
        if name:
708 516
            #pylint:disable-msg=E1101
709 599.1.1
            owner_query = owner_query.where(CollectionTable.c.name==name)
710
            watcher_query = watcher_query.where(CollectionTable.c.name==name)
711 516
            #pylint:enable-msg=E1101
712 329.1.23
            if version:
713
                # Limit the versions of those collections
714 516
                #pylint:disable-msg=E1101
715 644
                owner_query = owner_query.where(CollectionTable.c.version==\
716
                                                version)
717
                watcher_query = watcher_query.where(CollectionTable.c.version\
718
                                                    ==version)
719 516
                #pylint:enable-msg=E1101
720 329.1.23
721
        pkgs = {}
722
        # turn the query into a python object
723 48.8.1
        for pkg in itertools.chain(owner_query.execute(),
724
                watcher_query.execute()):
725 329.1.23
            additions = []
726 48.2.177
            additions.append(pkg[1])
727 599.1.1
            pkgs.setdefault(pkg[0], set()).update((pkg[1],))
728 329.1.23
729
        # Retrieve list of collection information for generating the
730
        # collection form
731 516
        #pylint:disable-msg=E1101
732 340
        collection_list = Collection.query.order_by('name').order_by('version')
733 516
        #pylint:enable-msg=E1101
734 329.1.23
        collections = {}
735 340
        for collection in collection_list:
736 329.1.23
            try:
737
                collections[collection.name].append(collection.version)
738
            except KeyError:
739
                collections[collection.name] = [collection.version]
740
741
        # Return the data
742 48.2.240
        return dict(title=_('%(app)s -- Notification List') % {
743
            'app': self.app_title}, packages=pkgs, collections=collections,
744
            name=name, version=version, eol=eol)
745 537.1.71
746 648
    @expose(template='mako:/plain/critpath.mak',
747
            as_format='plain', accept_format='text/plain',
748
            content_type='text/plain; charset=utf-8', #pylint:disable-msg=C0322
749
            format='text') #pylint:disable-msg=C0322
750 537.1.71
    @expose(allow_json=True)
751 537.1.72
    @validate(validators = {'collctn_list': SetOf(use_set=True,
752
            element_validator=IsCollectionSimpleNameRegex())})
753 537.1.71
    @error_handler()
754
    def critpath(self, collctn_list=None):
755
        '''Retrieve the list of packages that are critpath
756
757
        Critical path packages are subject to more testing or stringency of
758
        criteria for updating when a release occurs.
759
760
        :kwarg collctn_list: List of collection shortnames for which to
761
            retrieve the packages from.  The default is all non-EOL
762
            collections.
763
        :returns: dict keyed by collection shortname.  The values are the list
764
            of critpath packages for each collection
765
        '''
766
        # Check for validation errors requesting this form
767
        errors = jsonify_validation_errors()
768
        if errors:
769
            return errors
770
771
        pkg_names = select((BranchTable.c.branchname, PackageTable.c.name))\
772
                .where(and_(PackageTable.c.id==PackageListingTable.c.packageid,
773
                    PackageListingTable.c.collectionid==CollectionTable.c.id,
774 537.1.72
                    CollectionTable.c.id==BranchTable.c.collectionid,
775
                    PackageListingTable.c.critpath==True))
776 537.1.71
        if collctn_list:
777
            pkg_names = pkg_names.where(BranchTable.c.branchname.in_(collctn_list))
778
        else:
779 644
            pkg_names = pkg_names.where(CollectionTable.c.statuscode!=
780
                                        STATUS['EOL'])
781 537.1.71
782
        pkgs = {}
783
        for pkg in pkg_names.execute():
784
            try:
785
                pkgs[pkg[0]].add(pkg[1])
786
            except KeyError:
787 537.1.72
                pkgs[pkg[0]] = set()
788
                pkgs[pkg[0]].add(pkg[1])
789 537.1.71
790
        return dict(pkgs=pkgs)

Loggerhead 1.18.1 is a web-based interface for Bazaar branches