Пример #1
0
def list_packages():
    query = (
        db.query(
            Package.name.label('name'),
            Collection.name.label('collection'),
            # pylint:disable=no-member
            Package.state_string.label('state'),
            sql_if(
                Build.id != None,
                db.query(Build.task_id.label('task_id')).correlate(
                    Build).as_record()).label(
                        'last_complete_build')).join(Collection).outerjoin(
                            Build, (Package.last_complete_build_id == Build.id)
                            & Build.last_complete).order_by(Package.name))
    if 'name' in request.args:
        query = query.filter(Package.name.in_(request.args.getlist('name')))
    if 'collection' in request.args:
        query = query.filter(
            Collection.name.in_(request.args.getlist('collection')))

    result = (db.query(
        literal_column(
            "coalesce(array_to_json(array_agg(row_to_json(pkg_query)))::text, '[]')"
        ).label('q')).select_from(query.subquery('pkg_query')).scalar())

    return Response(result, mimetype='application/json')
Пример #2
0
def list_packages():
    """
    Return a list of all packages as JSON. Uses Postgres to generate all the JSON in a
    single query.

    Optional query parameters:
    collection: filter by collection name (list, literal match)
    name: filter by package name (list, literal match)

    Response format:
    [
        {
            "name": "foo",
            "collection": "f29",
            "state": "unresolved",
            "last_complete_build": {
                "task_id": 123
            }
        },
        ...
    ]
    """
    query = (
        db.query(
            Package.name.label('name'),
            Collection.name.label('collection'),
            # pylint:disable=no-member
            Package.state_string.label('state'),
            sql_if(
                Build.id != None,
                db.query(Build.task_id.label('task_id'))
                .correlate(Build)
                .as_record()
            ).label('last_complete_build')
        )
        .join(Collection)
        .outerjoin(
            Build,
            (Package.last_complete_build_id == Build.id) & Build.last_complete
        )
        .order_by(Package.name)
    )
    if 'name' in request.args:
        query = query.filter(Package.name.in_(request.args.getlist('name')))
    if 'collection' in request.args:
        query = query.filter(Collection.name.in_(request.args.getlist('collection')))

    result = (
        db.query(literal_column(
            "coalesce(array_to_json(array_agg(row_to_json(pkg_query)))::text, '[]')"
        ).label('q'))
        .select_from(query.subquery('pkg_query'))
        .scalar()
    )

    return Response(result, mimetype='application/json')
Пример #3
0
def edit_package(name):
    """
    Edit package attributes or groups. Everyone can edit attributes, group membership
    requires permissions.
    """
    form = forms.EditPackageForm()
    collection = g.collections_by_id.get(form.collection_id.data) or abort(400)
    if not form.validate_or_flash():
        return package_detail(name=name, form=form, collection=collection)
    package = db.query(Package)\
        .filter_by(name=name, collection_id=collection.id)\
        .first_or_404()

    # Interpret group checkboxes
    for key, prev_val in request.form.items():
        if key.startswith('group-prev-'):
            group = db.query(PackageGroup).get_or_404(int(key[len('group-prev-'):]))
            new_val = request.form.get('group-{}'.format(group.id))
            if bool(new_val) != (prev_val == 'true'):
                if not group.editable:
                    abort(403)
                if new_val:
                    data.set_group_content(session, group, [package.name], append=True)
                else:
                    data.set_group_content(session, group, [package.name], delete=True)

    # Using set_package_attribute to generate audit log events
    if form.tracked.data is not None:
        data.set_package_attribute(
            session, package, 'tracked',
            form.tracked.data,
        )
    if form.manual_priority.data is not None:
        data.set_package_attribute(
            session, package, 'manual_priority',
            form.manual_priority.data,
        )
    if form.arch_override.data is not None:
        data.set_package_attribute(
            session, package, 'arch_override',
            ' '.join(form.arch_override.data) or None,
        )
    if form.skip_resolution.data is not None:
        data.set_package_attribute(
            session, package, 'skip_resolution',
            form.skip_resolution.data,
        )
        if package.skip_resolution:
            package.resolved = None
            db.query(UnappliedChange).filter_by(package_id=package.id).delete()
    flash_ack("Package modified")

    db.commit()
    return redirect(url_for('package_detail', name=package.name) +
                    "?collection=" + collection.name)
Пример #4
0
def list_rebuild_requests(username):
    user = db.query(User).filter_by(name=username).first_or_404()
    requests = db.query(CoprRebuildRequest)\
        .filter(CoprRebuildRequest.user_id == user.id)\
        .order_by(CoprRebuildRequest.id.desc())\
        .all()
    form = RebuildRequestForm(
    ) if can_create_request() and user == g.user else None
    return render_template('list-rebuild-requests.html',
                           user=user,
                           requests=requests,
                           form=form)
Пример #5
0
def statistics():
    """
    Show global and per-package statistics about build times etc.
    Uses materialized views that are refreshed by backend's polling.
    """
    now = db.query(func.now()).scalar()
    scalar_stats = db.query(ScalarStats).one()
    resource_query = db.query(ResourceConsumptionStats)\
        .order_by(ResourceConsumptionStats.time.desc().nullslast())\
        .paginate(20)
    return render_template("stats.html", now=now, stats=scalar_stats,
                           packages=resource_query.items,
                           page=resource_query)
Пример #6
0
def get_global_notices():
    notices = [
        n.content
        for n in db.query(AdminNotice.content).filter_by(key="global_notice")
    ]
    for collection in g.current_collections:
        if collection.latest_repo_resolved is False:
            problems = db.query(BuildrootProblem)\
                .filter_by(collection_id=collection.id).all()
            notices.append("Base buildroot for {} is not installable. "
                           "Dependency problems:<br/>".format(collection) +
                           '<br/>'.join((p.problem for p in problems)))
    notices = list(map(Markup, notices))
    return notices
Пример #7
0
def statistics():
    """
    Show global and per-package statistics about build times etc.
    Uses materialized views that are refreshed by backend's polling.
    """
    now = db.query(func.now()).scalar()
    scalar_stats = db.query(ScalarStats).one()
    resource_query = db.query(ResourceConsumptionStats)\
        .order_by(ResourceConsumptionStats.time.desc().nullslast())\
        .paginate(20)
    return render_template("stats.html",
                           now=now,
                           stats=scalar_stats,
                           packages=resource_query.items,
                           page=resource_query)
Пример #8
0
def bugreport(name):
    """
    Redirect to a pre-filled bugzilla new bug page.
    """
    # Package must have last build, so we can have rebuild instructions.
    # It doesn't need to be failing, that's up to the user to check.
    package = db.query(Package)\
                .filter(Package.name == name)\
                .filter(Package.blocked == False)\
                .filter(Package.last_complete_build_id != None)\
                .filter(Package.collection_id == g.current_collections[0].id)\
                .options(joinedload(Package.last_complete_build))\
                .first() or abort(404)
    # Set up variables taht are interpolated into a template specified by configuration
    variables = package.srpm_nvra or abort(404)
    variables['package'] = package
    variables['collection'] = package.collection
    # Absolute URL of this instance, for the link back to Koschei
    external_url = frontend_config.get('external_url',
                                       request.host_url).rstrip('/')
    package_url = url_for('package_detail', name=package.name)
    variables['url'] = f'{external_url}{package_url}'
    template = get_config('bugreport.template')
    bug = {key: template[key].format(**variables) for key in template.keys()}
    bug['comment'] = dedent(bug['comment']).strip()
    query = urlencode(bug)
    bugreport_url = get_config('bugreport.url').format(query=query)
    return redirect(bugreport_url)
Пример #9
0
def bugreport(name):
    """
    Redirect to a pre-filled bugzilla new bug page.
    """
    # Package must have last build, so we can have rebuild instructions.
    # It doesn't need to be failing, that's up to the user to check.
    package = db.query(Package)\
                .filter(Package.name == name)\
                .filter(Package.blocked == False)\
                .filter(Package.last_complete_build_id != None)\
                .filter(Package.collection_id == g.current_collections[0].id)\
                .options(joinedload(Package.last_complete_build))\
                .first() or abort(404)
    # Set up variables taht are interpolated into a template specified by configuration
    variables = package.srpm_nvra or abort(404)
    variables['package'] = package
    variables['collection'] = package.collection
    # Absolute URL of this instance, for the link back to Koschei
    external_url = frontend_config.get('external_url', request.host_url).rstrip('/')
    package_url = url_for('package_detail', name=package.name)
    variables['url'] = f'{external_url}{package_url}'
    template = get_config('bugreport.template')
    bug = {key: template[key].format(**variables) for key in template.keys()}
    bug['comment'] = dedent(bug['comment']).strip()
    query = urlencode(bug)
    bugreport_url = get_config('bugreport.url').format(query=query)
    return redirect(bugreport_url)
Пример #10
0
def lookup_current_user():
    if request.endpoint == 'static':
        return
    g.user = None
    user_name = session.get('user', None)
    if user_name:
        g.user = db.query(m.User).filter_by(name=user_name).first()
Пример #11
0
def login():
    """
    Acknowledge the logged in user by adding it's name to session.
    The login itself must have had already happened in httpd.
    Adds the user to the database on first login.
    """
    if bypass_login:
        identity = "none"
        user_name = bypass_login
    else:
        identity = request.environ.get(user_env) or abort(501)
        user_name = re.match(user_re, identity).group(1)
    user = db.query(m.User).filter_by(name=user_name).first()
    if not user:
        user = m.User(name=user_name, admin=bool(bypass_login))
        db.add(user)
        db.commit()
        flash_info('New user "{}" was registered.'.format(user_name))
    session['user'] = user_name
    flash_ack('Logged in as user "{}" with identity "{}".'
              .format(user_name, identity))
    if user.admin:
        flash_info('You have admin privileges.')
    next_url = request.values.get("next", url_for('frontpage'))
    return redirect(next_url)
Пример #12
0
def populate_package_groups(packages):
    """
    Adds `visible_groups` field to package objects. It contains a list of PackageGroup
    objects that are visible to current user - user is on Group's ACL.
    Global groups are visible to everyone.

    Ideally, this would be expressed using a SQLA relationship instead, but realtionships
    don't allow additional inputs (current user).

    :param packages: object with base_id attribute that allows adding attributes
    """
    base_map = {}
    for package in packages:
        package.visible_groups = []
        base_map[package.base_id] = package
    filter_expr = PackageGroup.namespace == None
    if g.user:
        filter_expr |= GroupACL.user_id == g.user.id
    query = (
        db.query(PackageGroupRelation)
        .options(contains_eager(PackageGroupRelation.group))
        .filter(
            PackageGroupRelation.base_id.in_(base_map.keys())
            if base_map else false()
        )
        .join(PackageGroup)
        .filter(filter_expr)
        .order_by(PackageGroup.namespace, PackageGroup.name)
    )
    if g.user:
        query = query.outerjoin(GroupACL)
    for r in query:
        base_map[r.base_id].visible_groups.append(r.group)
Пример #13
0
def populate_package_groups(packages):
    """
    Adds `visible_groups` field to package objects. It contains a list of PackageGroup
    objects that are visible to current user - user is on Group's ACL.
    Global groups are visible to everyone.

    Ideally, this would be expressed using a SQLA relationship instead, but realtionships
    don't allow additional inputs (current user).

    :param packages: object with base_id attribute that allows adding attributes
    """
    base_map = {}
    for package in packages:
        package.visible_groups = []
        base_map[package.base_id] = package
    filter_expr = PackageGroup.namespace == None
    if g.user:
        filter_expr |= GroupACL.user_id == g.user.id
    query = (db.query(PackageGroupRelation).options(
        contains_eager(PackageGroupRelation.group)).filter(
            PackageGroupRelation.base_id.in_(base_map.keys()) if base_map else
            false()).join(PackageGroup).filter(filter_expr).order_by(
                PackageGroup.namespace, PackageGroup.name))
    if g.user:
        query = query.outerjoin(GroupACL)
    for r in query:
        base_map[r.base_id].visible_groups.append(r.group)
Пример #14
0
def confirm_delete_group(name, namespace=None):
    group = db.query(PackageGroup)\
              .options(joinedload(PackageGroup.packages))\
              .filter_by(name=name, namespace=namespace).first_or_404()
    if not group.editable:
        abort(401)
    return render_template('delete-group.html', group=group,
                           form=forms.EmptyForm())
Пример #15
0
def confirm_delete_group(name, namespace=None):
    group = db.query(PackageGroup)\
              .options(joinedload(PackageGroup.packages))\
              .filter_by(name=name, namespace=namespace).first_or_404()
    if not group.editable:
        abort(401)
    return render_template('delete-group.html',
                           group=group,
                           form=forms.EmptyForm())
Пример #16
0
def badge(name, collection):
    """
    Redirects to a status badge image for use in external pages,
    such as GitHub's README.md of a project.
    """
    c = g.collections_by_name.get(collection) or abort(404, "Collection not found")
    p = db.query(Package).filter_by(name=name, collection_id=c.id).first_or_404()
    image = 'images/badges/{}.{}'.format(p.state_string, request.path[-3:])
    return redirect(url_for('static', filename=image))
Пример #17
0
def build_detail(build_id):
    # pylint: disable=E1101
    build = db.query(Build)\
              .options(joinedload(Build.package),
                       subqueryload(Build.dependency_changes),
                       subqueryload(Build.build_arch_tasks))\
              .filter_by(id=build_id).first_or_404()
    return render_template("build-detail.html", build=build,
                           cancel_form=forms.EmptyForm())
Пример #18
0
def can_edit_group(group):
    """
    Whether the group is editable by the current user.

    Available as `editable` property of PackageGroup.
    """
    # TODO move to model_additions where it belongs
    return g.user and (g.user.admin or db.query(
        exists().where((GroupACL.user_id == g.user.id)
                       & (GroupACL.group_id == group.id))).scalar())
Пример #19
0
def build_detail(build_id):
    # pylint: disable=E1101
    build = db.query(Build)\
              .options(joinedload(Build.package),
                       subqueryload(Build.dependency_changes),
                       subqueryload(Build.build_arch_tasks))\
              .filter_by(id=build_id).first_or_404()
    return render_template("build-detail.html",
                           build=build,
                           cancel_form=forms.EmptyForm())
Пример #20
0
def lookup_current_user():
    """
    If logged in, bind the current user to g.user
    """
    if request.endpoint == 'static':
        return
    g.user = None
    user_name = session.get('user', None)
    if user_name:
        g.user = db.query(m.User).filter_by(name=user_name).first()
Пример #21
0
def rebuild_request_detail(request_id):
    rebuild_request = db.query(CoprRebuildRequest).options(
        subqueryload('resolution_changes'),
        joinedload('resolution_changes.package'),
        subqueryload('rebuilds'),
        joinedload('rebuilds.package'),
    ).get_or_404(request_id)
    return render_template('rebuild-request-detail.html',
                           request=rebuild_request,
                           form=EditRebuildForm())
Пример #22
0
def delete_group(name, namespace=None):
    group = db.query(PackageGroup)\
              .options(joinedload(PackageGroup.packages))\
              .filter_by(name=name, namespace=namespace).first_or_404()
    # Validate CSRF and permissions
    if not forms.EmptyForm().validate_or_flash() or not group.editable:
        abort(401)
    data.delete_group(session, group)
    db.commit()
    flash_ack("Group was deleted")
    return redirect(url_for('groups_overview'))
Пример #23
0
def collection_package_view(template, query_fn=None, **template_args):
    """
    Single-collection view of a list of packages.

    :param template: name of jinja2 template to be used
    :param query_fn: optional filter function of query -> filtered_query
    :param template_args: additional arguments passed to the template
    """
    # should be called only when len(g.current_collections) == 1
    collection = g.current_collections[0]
    # query current_priority separately as it's not a property of Package
    current_prio_expr = Package.current_priority_expression(
        collection=collection,
        last_build=Build,  # package is outerjoined with last_build
    )
    package_query = db.query(Package, current_prio_expr)\
        .filter(Package.collection_id == collection.id)
    if query_fn:
        package_query = query_fn(package_query.join(BasePackage))
    # whether to show untracked packages as well
    untracked = request.args.get('untracked') == '1'
    # determine correct ORDER BY
    order_name = request.args.get('order_by', 'running,state,name')
    order_map = {
        'name': [Package.name],
        'state': [Package.resolved, Reversed(Build.state)],
        'running': [Package.last_complete_build_id == Package.last_build_id],
        'task_id': [Build.task_id],
        'started': [Build.started],
        'current_priority': [NullsLastOrder(current_prio_expr)],
    }
    order_names, order = get_order(order_map, order_name)

    if not untracked:
        package_query = package_query.filter(Package.tracked == True)
    pkgs = package_query.filter(Package.blocked == False)\
                        .outerjoin(Package.last_build)\
                        .options(contains_eager(Package.last_build))\
                        .order_by(*order)

    page = pkgs.paginate(packages_per_page)
    # monkeypatch the priority as an attribute for ease of use
    for pkg, priority in page.items:
        pkg.current_priority = priority
    # extract only the package from the query results
    page.items = [pkg for pkg, _ in page.items]
    # monkeypatch visible package groups
    populate_package_groups(page.items)
    return render_template(template,
                           packages=page.items,
                           page=page,
                           order=order_names,
                           collection=collection,
                           **template_args)
Пример #24
0
def delete_group(name, namespace=None):
    group = db.query(PackageGroup)\
              .options(joinedload(PackageGroup.packages))\
              .filter_by(name=name, namespace=namespace).first_or_404()
    # Validate CSRF and permissions
    if not forms.EmptyForm().validate_or_flash() or not group.editable:
        abort(401)
    data.delete_group(session, group)
    db.commit()
    flash_ack("Group was deleted")
    return redirect(url_for('groups_overview'))
Пример #25
0
def get_global_notices():
    """
    Constructs a list of HTML elements representing current global notices taken from the
    DB table AdminNotice and also adds a warning if the base buildroot is unresolved.

    :return: List of directly renderable items. May be empty.
    """
    notices = [
        n.content
        for n in db.query(AdminNotice.content).filter_by(key="global_notice")
    ]
    for collection in g.current_collections:
        if collection.latest_repo_resolved is False:
            problems = db.query(BuildrootProblem)\
                .filter_by(collection_id=collection.id).all()
            notices.append("Base buildroot for {} is not installable. "
                           "Dependency problems:<br/>".format(collection) +
                           '<br/>'.join((p.problem for p in problems)))
    notices = list(map(Markup, notices))
    return notices
Пример #26
0
def badge(name, collection):
    """
    Redirects to a status badge image for use in external pages,
    such as GitHub's README.md of a project.
    """
    c = g.collections_by_name.get(collection) or abort(404,
                                                       "Collection not found")
    p = db.query(Package).filter_by(name=name,
                                    collection_id=c.id).first_or_404()
    image = 'images/badges/{}.{}'.format(p.state_string, request.path[-3:])
    return redirect(url_for('static', filename=image))
Пример #27
0
def group_detail(name=None, namespace=None):
    group = db.query(PackageGroup)\
              .filter_by(name=name, namespace=namespace).first_or_404()
    owners = ", ".join(owner.name for owner in group.owners)

    def query_fn(query):
        return query.outerjoin(PackageGroupRelation,
                               PackageGroupRelation.base_id == BasePackage.id)\
            .filter(PackageGroupRelation.group_id == group.id)

    return package_view("group-detail.html", query_fn=query_fn,
                        group=group, owners=owners)
Пример #28
0
def can_edit_group(group):
    """
    Whether the group is editable by the current user.

    Available as `editable` property of PackageGroup.
    """
    # TODO move to model_additions where it belongs
    return g.user and (g.user.admin or
                       db.query(exists()
                                .where((GroupACL.user_id == g.user.id) &
                                       (GroupACL.group_id == group.id)))
                       .scalar())
Пример #29
0
def edit_rebuild():
    form = EditRebuildForm()
    if not form.validate_on_submit():
        abort(400)
    rebuild = db.query(CoprRebuild)\
        .filter_by(request_id=form.request_id.data,
                   package_id=form.package_id.data)\
        .first_or_404()
    if not rebuild.request.editable:
        abort(403)
    if form.action.data == 'move-top':
        db.query(CoprRebuild)\
            .filter(CoprRebuild.request_id == rebuild.request_id)\
            .filter(CoprRebuild.state == None)\
            .filter(CoprRebuild.order < rebuild.order)\
            .update({'order': CoprRebuild.order + 1})
        rebuild.order = db.query(func.min(CoprRebuild.order) - 1)\
            .filter(CoprRebuild.request_id == rebuild.request_id)\
            .filter(CoprRebuild.state == None)\
            .scalar()
        # Moving to top should ensure the package will be scheduled
        rebuild.request.schedule_count += 1
        rebuild.request.state = 'in progress'
    elif form.action.data == 'remove':
        db.query(CoprRebuild)\
            .filter(CoprRebuild.request_id == rebuild.request_id)\
            .filter(CoprRebuild.order > rebuild.order)\
            .update({'order': CoprRebuild.order - 1})
        db.delete(rebuild)
    db.commit()
    return redirect(
        url_for('rebuild_request_detail', request_id=rebuild.request_id))
Пример #30
0
def diff_collections(name1, name2):
    """
    Compare two collections and return a list of packages with differing states
    packages as JSON. Uses Postgres to generate all the JSON in a single query.

    Response format:
    [
        {
            "name": "foo",
            "state: {
                "f25": "ok",
                "f26": "failing",
            }
        },
        ...
    ]
    """
    Package1 = aliased(Package)
    Package2 = aliased(Package)
    collection1 = db.query(Collection).filter_by(name=name1).first_or_404()
    collection2 = db.query(Collection).filter_by(name=name2).first_or_404()
    query = (
        db.query(
            BasePackage.name.label('name'),
            db.query(
                Package1.state_string.label(collection1.name),
                Package2.state_string.label(collection2.name),
            )
            .correlate(Package1, Package2)
            .as_record().label('state'),
        )
        .join(Package1, Package1.base_id == BasePackage.id)
        .join(Package2, Package2.base_id == BasePackage.id)
        .filter(Package1.state_string != Package2.state_string)
        .filter(Package1.collection_id == collection1.id)
        .filter(Package2.collection_id == collection2.id)
        .order_by(BasePackage.name)
    )

    return Response(query.json(), mimetype='application/json')
Пример #31
0
def collection_package_view(template, query_fn=None, **template_args):
    """
    Single-collection view of a list of packages.

    :param template: name of jinja2 template to be used
    :param query_fn: optional filter function of query -> filtered_query
    :param template_args: additional arguments passed to the template
    """
    # should be called only when len(g.current_collections) == 1
    collection = g.current_collections[0]
    # query current_priority separately as it's not a property of Package
    current_prio_expr = Package.current_priority_expression(
        collection=collection,
        last_build=Build,  # package is outerjoined with last_build
    )
    package_query = db.query(Package, current_prio_expr)\
        .filter(Package.collection_id == collection.id)
    if query_fn:
        package_query = query_fn(package_query.join(BasePackage))
    # whether to show untracked packages as well
    untracked = request.args.get('untracked') == '1'
    # determine correct ORDER BY
    order_name = request.args.get('order_by', 'running,state,name')
    order_map = {
        'name': [Package.name],
        'state': [Package.resolved, Reversed(Build.state)],
        'running': [Package.last_complete_build_id == Package.last_build_id],
        'task_id': [Build.task_id],
        'started': [Build.started],
        'current_priority': [NullsLastOrder(current_prio_expr)],
    }
    order_names, order = get_order(order_map, order_name)

    if not untracked:
        package_query = package_query.filter(Package.tracked == True)
    pkgs = package_query.filter(Package.blocked == False)\
                        .outerjoin(Package.last_build)\
                        .options(contains_eager(Package.last_build))\
                        .order_by(*order)

    page = pkgs.paginate(packages_per_page)
    # monkeypatch the priority as an attribute for ease of use
    for pkg, priority in page.items:
        pkg.current_priority = priority
    # extract only the package from the query results
    page.items = [pkg for pkg, _ in page.items]
    # monkeypatch visible package groups
    populate_package_groups(page.items)
    return render_template(template, packages=page.items, page=page,
                           order=order_names, collection=collection,
                           **template_args)
Пример #32
0
def group_detail(name=None, namespace=None):
    group = db.query(PackageGroup)\
              .filter_by(name=name, namespace=namespace).first_or_404()
    owners = ", ".join(owner.name for owner in group.owners)

    def query_fn(query):
        return query.outerjoin(PackageGroupRelation,
                               PackageGroupRelation.base_id == BasePackage.id)\
            .filter(PackageGroupRelation.group_id == group.id)

    return package_view("group-detail.html",
                        query_fn=query_fn,
                        group=group,
                        owners=owners)
Пример #33
0
def collection_list():
    groups = db.query(CollectionGroup)\
        .options(joinedload(CollectionGroup.collections))\
        .order_by(CollectionGroup.name)\
        .all()
    # collections belonging to a category
    categorized_ids = {
        collection.id for group in groups for collection in group.collections
    }
    # collections that don't belong to any category and should be displayed in
    # "Uncategorized collections" pseudo-category.
    uncategorized = [
        collection for collection in g.collections if collection.id not in categorized_ids
    ]
    return render_template("list-collections.html", groups=groups,
                           uncategorized=uncategorized)
Пример #34
0
def add_packages():
    """
    Mark multiple packages as tracked. Optionally add them to a group.
    """
    form = forms.AddPackagesForm()
    if request.method == 'POST':
        if not form.validate_or_flash():
            return render_template("add-packages.html", form=form)
        names = set(form.packages.data)
        try:
            collection = [
                c for c in g.collections if c.name == form.collection.data
            ][0]
        except IndexError:
            abort(404)

        try:
            added = data.track_packages(session, collection, names)
        except data.PackagesDontExist as e:
            db.rollback()
            flash_nak(str(e))
            # frontend doesn't have Koji access, so it needs to rely on backend's polling
            flash_nak(
                dedent("""
                If a package has been just created, it is possible that it
                hasn't been propagated to our database yet. In that case,
                please, try again later.
            """))
            return render_template("add-packages.html", form=form)

        if form.group.data:
            namespace, name = PackageGroup.parse_name(form.group.data)
            group = db.query(PackageGroup)\
                      .filter_by(namespace=namespace, name=name)\
                      .first_or_404()
            if not group.editable:
                abort(400)
            data.set_group_content(session, group, names, append=True)

        flash_ack("Packages added: {}".format(','.join(p.name for p in added)))
        db.commit()
        return redirect(request.form.get('next') or url_for('frontpage'))
    return render_template("add-packages.html", form=form)
Пример #35
0
def collection_list():
    groups = db.query(CollectionGroup)\
        .options(joinedload(CollectionGroup.collections))\
        .order_by(CollectionGroup.name)\
        .all()
    # collections belonging to a category
    categorized_ids = {
        collection.id
        for group in groups for collection in group.collections
    }
    # collections that don't belong to any category and should be displayed in
    # "Uncategorized collections" pseudo-category.
    uncategorized = [
        collection for collection in g.collections
        if collection.id not in categorized_ids
    ]
    return render_template("list-collections.html",
                           groups=groups,
                           uncategorized=uncategorized)
Пример #36
0
def login():
    if bypass_login:
        identity = "none"
        user_name = bypass_login
    else:
        identity = request.environ.get(user_env) or abort(501)
        user_name = re.match(user_re, identity).group(1)
    user = db.query(m.User).filter_by(name=user_name).first()
    if not user:
        user = m.User(name=user_name, admin=bool(bypass_login))
        db.add(user)
        db.commit()
        flash_info('New user "{}" was registered.'.format(user_name))
    session['user'] = user_name
    flash_ack('Logged in as user "{}" with identity "{}".'
              .format(user_name, identity))
    if user.admin:
        flash_info('You have admin privileges.')
    next_url = request.values.get("next", url_for('frontpage'))
    return redirect(next_url)
Пример #37
0
def add_packages():
    """
    Mark multiple packages as tracked. Optionally add them to a group.
    """
    form = forms.AddPackagesForm()
    if request.method == 'POST':
        if not form.validate_or_flash():
            return render_template("add-packages.html", form=form)
        names = set(form.packages.data)
        try:
            collection = [c for c in g.collections
                          if c.name == form.collection.data][0]
        except IndexError:
            abort(404)

        try:
            added = data.track_packages(session, collection, names)
        except data.PackagesDontExist as e:
            db.rollback()
            flash_nak(str(e))
            # frontend doesn't have Koji access, so it needs to rely on backend's polling
            flash_nak(dedent("""
                If a package has been just created, it is possible that it
                hasn't been propagated to our database yet. In that case,
                please, try again later.
            """))
            return render_template("add-packages.html", form=form)

        if form.group.data:
            namespace, name = PackageGroup.parse_name(form.group.data)
            group = db.query(PackageGroup)\
                      .filter_by(namespace=namespace, name=name)\
                      .first_or_404()
            if not group.editable:
                abort(400)
            data.set_group_content(session, group, names, append=True)

        flash_ack("Packages added: {}".format(','.join(p.name for p in added)))
        db.commit()
        return redirect(request.form.get('next') or url_for('frontpage'))
    return render_template("add-packages.html", form=form)
Пример #38
0
def cancel_build(build_id):
    """
    Requests cancellation of a build by marking the build in the DB.
    Doesn't do the cancellation itself, as frontend doens't have access to Koji.
    Backend polls for the atttribute.
    """
    if not g.user.admin:
        abort(403)
    build = db.query(Build).filter_by(id=build_id).first_or_404()
    if forms.EmptyForm().validate_or_flash():
        if build.state != Build.RUNNING:
            flash_nak("Only running builds can be canceled.")
        elif build.cancel_requested:
            flash_nak("Build already has pending cancelation request.")
        else:
            flash_ack("Cancelation request sent.")
            session.log_user_action(
                "Build (id={build.id}, task_id={build.task_id}) cancelation requested"
                .format(build=build))
            build.cancel_requested = True
            db.commit()
    return redirect(url_for('package_detail', name=build.package.name))
Пример #39
0
def cancel_build(build_id):
    """
    Requests cancellation of a build by marking the build in the DB.
    Doesn't do the cancellation itself, as frontend doens't have access to Koji.
    Backend polls for the atttribute.
    """
    if not g.user.admin:
        abort(403)
    build = db.query(Build).filter_by(id=build_id).first_or_404()
    if forms.EmptyForm().validate_or_flash():
        if build.state != Build.RUNNING:
            flash_nak("Only running builds can be canceled.")
        elif build.cancel_requested:
            flash_nak("Build already has pending cancelation request.")
        else:
            flash_ack("Cancelation request sent.")
            session.log_user_action(
                "Build (id={build.id}, task_id={build.task_id}) cancelation requested"
                .format(build=build)
            )
            build.cancel_requested = True
            db.commit()
    return redirect(url_for('package_detail', name=build.package.name))
Пример #40
0
def groups_overview():
    groups = db.query(PackageGroup)\
               .options(undefer(PackageGroup.package_count))\
               .filter_by(namespace=None)\
               .order_by(PackageGroup.name).all()
    return render_template("list-groups.html", groups=groups)
Пример #41
0
def unified_package_view(template, query_fn=None, **template_args):
    """
    View of package in multiple collections at the same time.

    :param template: name of jinja2 template to be used
    :param query_fn: optional filter function of query -> filtered_query
    :param template_args: additional arguments passed to the template
    """
    # whether to include untracked packages as well
    untracked = request.args.get('untracked') == '1'
    order_name = request.args.get('order_by', 'running,failing,name')
    # all of the following variables are iteratively built in the follwoing loops (fold)
    # columns queried for each collection
    exprs = []
    # aliased package tables for each collection
    tables = []
    # whether the package has a running build in any of the collections
    running_build_expr = false()
    # whether the package has a failed build or is unresolved in any of the collections
    failing_expr = false()
    # whether the package is tracked in any of the collections
    tracked_expr = false()
    order_map = {'name': [BasePackage.name]}
    # All collections are queried in single query that has variable number of tables and
    # columns. For each collection there's an additional joined aliased table.
    # Now, build the aliased tables (no joins yet), their columns and boolean properties
    for collection in g.current_collections:
        table = aliased(Package)
        tables.append(table)
        exprs.append(table.tracked.label('tracked{}'.format(collection.id)))
        exprs.append(table.resolved.label('resolved{}'.format(collection.id)))
        exprs.append(table.last_complete_build_state
                     .label('state{}'.format(collection.id)))
        running_build_expr |= table.last_build_id != table.last_complete_build_id
        failing_expr |= table.last_complete_build_state == Build.FAILED
        failing_expr |= table.resolved == False
        tracked_expr |= table.tracked == True
    running_build_expr = func.coalesce(running_build_expr, false())
    failing_expr = func.coalesce(failing_expr, false())
    # Declare query columns
    query = db.query(BasePackage.name, BasePackage.id.label('base_id'),
                     running_build_expr.label('has_running_build'),
                     *exprs).filter(~BasePackage.all_blocked)
    if not untracked:
        # TODO I'm not sure if this is necessary.
        # We filter by "tracked" in JOIN's ON exprs
        query = query.filter(tracked_expr)
    # Build joins and collection-specific order expressions
    for collection, table in zip(g.current_collections, tables):
        on_expr = BasePackage.id == table.base_id
        on_expr &= table.collection_id == collection.id
        on_expr &= ~table.blocked
        if not untracked:
            on_expr &= table.tracked
        query = query.outerjoin(table, on_expr)
        order_map['state-' + collection.name] = \
            [table.resolved, Reversed(table.last_complete_build_state)]
    if query_fn:
        query = query_fn(query)
    order_map['running'] = [Reversed(running_build_expr)]
    order_map['failing'] = [Reversed(failing_expr)]

    order_names, order = get_order(order_map, order_name)

    page = query.order_by(*order).paginate(packages_per_page)
    page.items = list(map(UnifiedPackage, page.items))
    # monkey-patch visible groups on the row
    populate_package_groups(page.items)
    return render_template(template, packages=page.items, page=page,
                           order=order_names, collection=None, **template_args)
Пример #42
0
def affected_by(dep_name):
    """
    Display which packages are possibly affected by given dependency change.
    """
    if len(g.current_collections) != 1:
        abort(400)
    collection = g.current_collections[0]
    try:
        evr1 = RpmEVR(
            int(request.args['epoch1']),
            request.args['version1'],
            request.args['release1']
        )
        evr2 = RpmEVR(
            int(request.args['epoch2']),
            request.args['version2'],
            request.args['release2']
        )
    except (KeyError, ValueError):
        abort(400)

    # Dependencies in the evr1 to evr2 interval
    # Note that evr comparisons are overloaded custom comparators that invoke RPM-correct
    # comparisons implemented in rpmvercmp.sql
    deps_in = (
        db.query(Dependency.id)
        .filter(Dependency.name == dep_name)
        .filter(Dependency.evr > evr1)
        .filter(Dependency.evr < evr2)
        .cte('deps_in')
    )
    # Dependencies with greater evr than evr2
    deps_higher = (
        db.query(Dependency.id)
        .filter(Dependency.name == dep_name)
        .filter(Dependency.evr >= evr2)
        .cte('deps_higher')
    )
    # Dependencies with lesser evr than evr1
    deps_lower = (
        db.query(Dependency.id)
        .filter(Dependency.name == dep_name)
        .filter(Dependency.evr <= evr1)
        .cte('deps_lower')
    )
    # Get only changes where the prev_evr to curr_evr interval overlaps with evr1 to evr2
    filtered_changes = union(
        # Changes with previous evr in the evr1 to evr2 interval
        db.query(AppliedChange)
        .filter(AppliedChange.prev_dep_id.in_(db.query(deps_in))),
        # Changes with current evr in the evr1 to evr2 interval
        db.query(AppliedChange)
        .filter(AppliedChange.curr_dep_id.in_(db.query(deps_in))),
        # Changes with both evrs "around" the evr1 to evr2 interval
        db.query(AppliedChange)
        .filter(
            (AppliedChange.prev_dep_id.in_(db.query(deps_lower))) &
            (AppliedChange.curr_dep_id.in_(db.query(deps_higher)))
        ),
    ).alias('filtered_changes')

    prev_build = aliased(Build)
    # Get a subquery for previous build state
    subq = db.query(prev_build.state.label('prev_state'))\
        .order_by(prev_build.started.desc())\
        .filter(prev_build.started < Build.started)\
        .filter(prev_build.package_id == Build.package_id)\
        .limit(1)\
        .correlate().as_scalar()
    prev_dep = aliased(Dependency)
    curr_dep = aliased(Dependency)
    failed = (
        db.query(
            prev_dep.name.label('dep_name'),
            prev_dep.evr.label('prev_evr'),
            curr_dep.evr.label('curr_evr'),
            AppliedChange.distance,
            Build.id.label('build_id'),
            Build.state.label('build_state'),
            Build.started.label('build_started'),
            Package.name.label('package_name'),
            Package.resolved.label('package_resolved'),
            Package.last_complete_build_state.label('package_lb_state'),
            subq.label('prev_build_state'),
        )
        .select_entity_from(filtered_changes)
        .join(prev_dep, AppliedChange.prev_dep)
        .join(curr_dep, AppliedChange.curr_dep)
        .join(AppliedChange.build).join(Build.package)
        .filter_by(blocked=False, tracked=True, collection_id=collection.id)
        # Show only packages where the build after failed, but the previous one was ok
        .filter(Build.state == Build.FAILED)
        .filter(subq != Build.FAILED)
        .order_by(AppliedChange.distance, Build.started.desc())
        .all()
    )

    # Auxiliary function to compute state string for the query row
    def package_state(row):
        return Package(
            tracked=True,
            blocked=False,
            resolved=row.package_resolved,
            last_complete_build_state=row.package_lb_state,
        ).state_string

    return render_template("affected-by.html", package_state=package_state,
                           dep_name=dep_name, evr1=evr1, evr2=evr2,
                           collection=collection, failed=failed)
Пример #43
0
def package_detail(name, form=None, collection=None):
    if not collection:
        collection = g.current_collections[0]

    g.current_collections = [collection]

    base = db.query(BasePackage).filter_by(name=name).first_or_404()
    # Get packages for all collections, so that we can display
    # "State in other collections" table
    packages = {p.collection_id: p for p in db.query(Package).filter_by(base_id=base.id)}

    # assign packages to collections in the right order
    package = None  # the current package, may stay None
    all_packages = []
    for coll in g.collections:
        p = packages.get(coll.id)
        if p:
            all_packages.append((coll, p))
            if coll is collection:
                package = p

    # prepare group checkboxes
    base.global_groups = db.query(PackageGroup)\
        .join(PackageGroupRelation)\
        .filter(PackageGroupRelation.base_id == base.id)\
        .filter(PackageGroup.namespace == None)\
        .all()
    base.user_groups = []
    base.available_groups = []
    if g.user:
        user_groups = \
            db.query(PackageGroup,
                     func.bool_or(PackageGroupRelation.base_id == base.id))\
            .outerjoin(PackageGroupRelation)\
            .join(GroupACL)\
            .filter(GroupACL.user_id == g.user.id)\
            .order_by(PackageGroup.namespace.nullsfirst(), PackageGroup.name)\
            .group_by(PackageGroup.id)\
            .distinct().all()
        base.user_groups = [group for group, checked in user_groups
                            if checked and group.namespace]
        base.available_groups = [group for group, checked in user_groups
                                 if not checked]

    # History entry pagination pivot timestamp
    # We only display entries older than this
    last_seen_ts = request.args.get('last_seen_ts')
    if last_seen_ts:
        try:
            last_seen_ts = int(last_seen_ts)
        except ValueError:
            abort(400)

    def to_ts(col):
        return cast(func.extract('EPOCH', col), Integer)

    entries = None

    if package:
        # set current priority
        package.current_priority = db.query(
            Package.current_priority_expression(
                collection=package.collection,
                last_build=package.last_build,
            )
        ).filter(Package.id == package.id).scalar()
        # prepare history entries - builds and resolution changes
        builds = db.query(Build)\
            .filter_by(package_id=package.id)\
            .filter(to_ts(Build.started) < last_seen_ts
                    if last_seen_ts else true())\
            .options(subqueryload(Build.dependency_changes),
                     subqueryload(Build.build_arch_tasks))\
            .order_by(Build.started.desc())\
            .limit(builds_per_page)\
            .all()
        resolutions = db.query(ResolutionChange)\
            .filter_by(package_id=package.id)\
            .filter(to_ts(ResolutionChange.timestamp) < last_seen_ts
                    if last_seen_ts else true())\
            .options(joinedload(ResolutionChange.problems))\
            .order_by(ResolutionChange.timestamp.desc())\
            .limit(builds_per_page)\
            .all()

        entries = sorted(
            builds + resolutions,
            key=lambda x: getattr(x, 'started', None) or getattr(x, 'timestamp'),
            reverse=True,
        )[:builds_per_page]

        if not form:
            form = forms.EditPackageForm(
                tracked=package.tracked,
                collection_id=package.collection_id,
                manual_priority=package.manual_priority,
                arch_override=(package.arch_override or '').split(' '),
                skip_resolution=package.skip_resolution,
            )

    # Note: package might be None
    return render_template(
        "package-detail.html",
        base=base,
        package=package,
        collection=collection,
        form=form,
        entries=entries,
        all_packages=all_packages,
        is_continuation=bool(last_seen_ts),
        is_last=len(entries) < builds_per_page if package else True,
    )
Пример #44
0
def package_detail(name, form=None, collection=None):
    if not collection:
        collection = g.current_collections[0]

    g.current_collections = [collection]

    base = db.query(BasePackage).filter_by(name=name).first_or_404()
    # Get packages for all collections, so that we can display
    # "State in other collections" table
    packages = {
        p.collection_id: p
        for p in db.query(Package).filter_by(base_id=base.id)
    }

    # assign packages to collections in the right order
    package = None  # the current package, may stay None
    all_packages = []
    for coll in g.collections:
        p = packages.get(coll.id)
        if p:
            all_packages.append((coll, p))
            if coll is collection:
                package = p

    # prepare group checkboxes
    base.global_groups = db.query(PackageGroup)\
        .join(PackageGroupRelation)\
        .filter(PackageGroupRelation.base_id == base.id)\
        .filter(PackageGroup.namespace == None)\
        .all()
    base.user_groups = []
    base.available_groups = []
    if g.user:
        user_groups = \
            db.query(PackageGroup,
                     func.bool_or(PackageGroupRelation.base_id == base.id))\
            .outerjoin(PackageGroupRelation)\
            .join(GroupACL)\
            .filter(GroupACL.user_id == g.user.id)\
            .order_by(PackageGroup.namespace.nullsfirst(), PackageGroup.name)\
            .group_by(PackageGroup.id)\
            .distinct().all()
        base.user_groups = [
            group for group, checked in user_groups
            if checked and group.namespace
        ]
        base.available_groups = [
            group for group, checked in user_groups if not checked
        ]

    # History entry pagination pivot timestamp
    # We only display entries older than this
    last_seen_ts = request.args.get('last_seen_ts')
    if last_seen_ts:
        try:
            last_seen_ts = int(last_seen_ts)
        except ValueError:
            abort(400)

    def to_ts(col):
        return cast(func.extract('EPOCH', col), Integer)

    entries = None

    if package:
        # set current priority
        package.current_priority = db.query(
            Package.current_priority_expression(
                collection=package.collection,
                last_build=package.last_build,
            )).filter(Package.id == package.id).scalar()
        # prepare history entries - builds and resolution changes
        builds = db.query(Build)\
            .filter_by(package_id=package.id)\
            .filter(to_ts(Build.started) < last_seen_ts
                    if last_seen_ts else true())\
            .options(subqueryload(Build.dependency_changes),
                     subqueryload(Build.build_arch_tasks))\
            .order_by(Build.started.desc())\
            .limit(builds_per_page)\
            .all()
        resolutions = db.query(ResolutionChange)\
            .filter_by(package_id=package.id)\
            .filter(to_ts(ResolutionChange.timestamp) < last_seen_ts
                    if last_seen_ts else true())\
            .options(joinedload(ResolutionChange.problems))\
            .order_by(ResolutionChange.timestamp.desc())\
            .limit(builds_per_page)\
            .all()

        entries = sorted(
            builds + resolutions,
            key=lambda x: getattr(x, 'started', None) or getattr(
                x, 'timestamp'),
            reverse=True,
        )[:builds_per_page]

        if not form:
            form = forms.EditPackageForm(
                tracked=package.tracked,
                collection_id=package.collection_id,
                manual_priority=package.manual_priority,
                arch_override=(package.arch_override or '').split(' '),
                skip_resolution=package.skip_resolution,
            )

    # Note: package might be None
    return render_template(
        "package-detail.html",
        base=base,
        package=package,
        collection=collection,
        form=form,
        entries=entries,
        all_packages=all_packages,
        is_continuation=bool(last_seen_ts),
        is_last=len(entries) < builds_per_page if package else True,
    )
Пример #45
0
def edit_group(name, namespace=None):
    group = db.query(PackageGroup)\
              .options(joinedload(PackageGroup.packages))\
              .filter_by(name=name, namespace=namespace).first_or_404()
    return process_group_form(group=group)
Пример #46
0
def groups_overview():
    groups = db.query(PackageGroup)\
               .options(undefer(PackageGroup.package_count))\
               .filter_by(namespace=None)\
               .order_by(PackageGroup.name).all()
    return render_template("list-groups.html", groups=groups)
Пример #47
0
def affected_by(dep_name):
    """
    Display which packages are possibly affected by given dependency change.
    """
    if len(g.current_collections) != 1:
        abort(400)
    collection = g.current_collections[0]
    try:
        evr1 = RpmEVR(int(request.args['epoch1']), request.args['version1'],
                      request.args['release1'])
        evr2 = RpmEVR(int(request.args['epoch2']), request.args['version2'],
                      request.args['release2'])
    except (KeyError, ValueError):
        abort(400)

    # Dependencies in the evr1 to evr2 interval
    # Note that evr comparisons are overloaded custom comparators that invoke RPM-correct
    # comparisons implemented in rpmvercmp.sql
    deps_in = (db.query(Dependency.id).filter(
        Dependency.name == dep_name).filter(Dependency.evr > evr1).filter(
            Dependency.evr < evr2).cte('deps_in'))
    # Dependencies with greater evr than evr2
    deps_higher = (db.query(
        Dependency.id).filter(Dependency.name == dep_name).filter(
            Dependency.evr >= evr2).cte('deps_higher'))
    # Dependencies with lesser evr than evr1
    deps_lower = (db.query(
        Dependency.id).filter(Dependency.name == dep_name).filter(
            Dependency.evr <= evr1).cte('deps_lower'))
    # Get only changes where the prev_evr to curr_evr interval overlaps with evr1 to evr2
    filtered_changes = union(
        # Changes with previous evr in the evr1 to evr2 interval
        db.query(AppliedChange).filter(
            AppliedChange.prev_dep_id.in_(db.query(deps_in))),
        # Changes with current evr in the evr1 to evr2 interval
        db.query(AppliedChange).filter(
            AppliedChange.curr_dep_id.in_(db.query(deps_in))),
        # Changes with both evrs "around" the evr1 to evr2 interval
        db.query(AppliedChange).filter(
            (AppliedChange.prev_dep_id.in_(db.query(deps_lower)))
            & (AppliedChange.curr_dep_id.in_(db.query(deps_higher)))),
    ).alias('filtered_changes')

    prev_build = aliased(Build)
    # Get a subquery for previous build state
    subq = db.query(prev_build.state.label('prev_state'))\
        .order_by(prev_build.started.desc())\
        .filter(prev_build.started < Build.started)\
        .filter(prev_build.package_id == Build.package_id)\
        .limit(1)\
        .correlate().as_scalar()
    prev_dep = aliased(Dependency)
    curr_dep = aliased(Dependency)
    failed = (
        db.query(
            prev_dep.name.label('dep_name'),
            prev_dep.evr.label('prev_evr'),
            curr_dep.evr.label('curr_evr'),
            AppliedChange.distance,
            Build.id.label('build_id'),
            Build.state.label('build_state'),
            Build.started.label('build_started'),
            Package.name.label('package_name'),
            Package.resolved.label('package_resolved'),
            Package.last_complete_build_state.label('package_lb_state'),
            subq.label('prev_build_state'),
        ).select_entity_from(filtered_changes).join(
            prev_dep, AppliedChange.prev_dep).join(
                curr_dep,
                AppliedChange.curr_dep).join(AppliedChange.build).join(
                    Build.package).filter_by(blocked=False,
                                             tracked=True,
                                             collection_id=collection.id)
        # Show only packages where the build after failed, but the previous one was ok
        .filter(Build.state == Build.FAILED).filter(
            subq != Build.FAILED).order_by(AppliedChange.distance,
                                           Build.started.desc()).all())

    # Auxiliary function to compute state string for the query row
    def package_state(row):
        return Package(
            tracked=True,
            blocked=False,
            resolved=row.package_resolved,
            last_complete_build_state=row.package_lb_state,
        ).state_string

    return render_template("affected-by.html",
                           package_state=package_state,
                           dep_name=dep_name,
                           evr1=evr1,
                           evr2=evr2,
                           collection=collection,
                           failed=failed)
Пример #48
0
def edit_package(name):
    """
    Edit package attributes or groups. Everyone can edit attributes, group membership
    requires permissions.
    """
    form = forms.EditPackageForm()
    collection = g.collections_by_id.get(form.collection_id.data) or abort(400)
    if not form.validate_or_flash():
        return package_detail(name=name, form=form, collection=collection)
    package = db.query(Package)\
        .filter_by(name=name, collection_id=collection.id)\
        .first_or_404()

    # Interpret group checkboxes
    for key, prev_val in request.form.items():
        if key.startswith('group-prev-'):
            group = db.query(PackageGroup).get_or_404(
                int(key[len('group-prev-'):]))
            new_val = request.form.get('group-{}'.format(group.id))
            if bool(new_val) != (prev_val == 'true'):
                if not group.editable:
                    abort(403)
                if new_val:
                    data.set_group_content(session,
                                           group, [package.name],
                                           append=True)
                else:
                    data.set_group_content(session,
                                           group, [package.name],
                                           delete=True)

    # Using set_package_attribute to generate audit log events
    if form.tracked.data is not None:
        data.set_package_attribute(
            session,
            package,
            'tracked',
            form.tracked.data,
        )
    if form.manual_priority.data is not None:
        data.set_package_attribute(
            session,
            package,
            'manual_priority',
            form.manual_priority.data,
        )
    if form.arch_override.data is not None:
        data.set_package_attribute(
            session,
            package,
            'arch_override',
            ' '.join(form.arch_override.data) or None,
        )
    if form.skip_resolution.data is not None:
        data.set_package_attribute(
            session,
            package,
            'skip_resolution',
            form.skip_resolution.data,
        )
        if package.skip_resolution:
            package.resolved = None
            db.query(UnappliedChange).filter_by(package_id=package.id).delete()
    flash_ack("Package modified")

    db.commit()
    return redirect(
        url_for('package_detail', name=package.name) + "?collection=" +
        collection.name)
Пример #49
0
def edit_group(name, namespace=None):
    group = db.query(PackageGroup)\
              .options(joinedload(PackageGroup.packages))\
              .filter_by(name=name, namespace=namespace).first_or_404()
    return process_group_form(group=group)