<commit_before>from __future__ import print_function
from future_builtins import zip
import numpy as np
import scipy.sparse as sp
import csr_utils
import misc_utils as util
from util.assert_helpers import assertEqual, assertEqLength
from ordering.minimum_degree import min_degree_ordering

# TODO 1. Do ordering within the blocks, along the upper envelope
#         (but postpone block orderings)
#         upper envelope: apparently, the blocks are in lower triangular form
#      2. Code gen for AD

class BlockSparsityPattern:
    def __init__(self, name, nrows, ncols, nzeros):
        self.name = name
        self.nrows = nrows
        self.ncols = ncols
        self.nzeros = nzeros
        self.row_names = None
        self.col_names = None
        self.row_suffixes = { } # suffix name -> np.array of (index, value)
        self.col_suffixes = { } # suffix name -> np.array of (index, value)
        # Sparsity pattern of the Jacobian in the CSR format
        self.csr_data    = np.zeros(nzeros,  dtype=np.float64)
        self.csr_indices = np.zeros(nzeros,  dtype=np.int32)
        self.csr_indptr  = np.zeros(nrows+1, dtype=np.int32)
        self.csr_mat     = None   # View of (data, indices, indptr) as a csr_mat
        self.col_len = None # redundant info, can be computed from Jacobian too
        # These below are only needed to build the Jacobian, factor them out?
        self.csr_pos     = int(0) # Counter needed to build the csr_mat
        self.dbg_prev_row= int(-1)# Check whether the J segments are ordered
        # The data below comes from block reconstruction
        self.row_permutation  = None # AMPL row indices in permuted order
        self.col_permutation  = None # AMPL col indices in permuted order
        # TODO Inverse permutations are only used by plot, compute only there?
        self.inverse_row_perm = None # inverse of row_permutation
        self.inverse_col_perm = None # inverse of col_permutation
        # Block boundaries
        self.rblx = None # adjancent elements of blx give the block slices
        self.cblx = None # block beg end indices: zip(blx[:-1],blx[1:])
        # indices in the ith block = permutation[slice(beg, end)]
        # block count: len(blx)-1

def itr_index_block_slice(blx):
    for i, beg_end in enumerate(zip(blx[:-1],blx[1:])):
        yield i, slice(*beg_end)

def get_block_boundaries(bsp, i, j):
    return bsp.rblx[i], bsp.rblx[i+1], bsp.cblx[j], bsp.cblx[j+1]

################################################################################
# partition: np.array of (index, value) pairs, where value is the block id,
# i.e. partition['index'] gives the indices, partition['values'] the block ids

def set_permutation_with_block_boundaries(bsp):
    blockid = 'blockid'
    if (blockid not in bsp.row_suffixes) or (blockid not in bsp.col_suffixes):
        print('WARNING: No row and/or col partitions!')
        make_one_big_fake_block(bsp)
        return
    row_partition = bsp.row_suffixes[blockid]
    col_partition = bsp.col_suffixes[blockid]
    bsp.row_permutation, bsp.rblx = reconstruct(row_partition)
    bsp.col_permutation, bsp.cblx = reconstruct(col_partition)
    set_inverse_permutations(bsp)
    # The rest of this function is just debugging
    assertEqLength(bsp.rblx, bsp.cblx) # it is not necessary in the general case
    print('ROWS')
    dbg_show(row_partition, bsp.row_permutation, bsp.rblx)
    print('COLS')
    dbg_show(col_partition, bsp.col_permutation, bsp.cblx)
    # FIXME Hack to get block profiles
    get_permuted_block_profiles(bsp)
    set_min_degree_order(bsp)

def reconstruct(partition):
    # Sorts partition in place by block ids
    # returns: tuple of permutation vector, block boundaries as (beg, end) tuple
    sort_by_block_id(partition)
    blx = get_blocks(partition)
    check_block_ids(partition, len(blx)-1)
    return partition['index'], blx

def sort_by_block_id(partition):
    # in place, stable sort by block id (by 'value')
    np.ndarray.sort(partition, kind='mergesort', order='value')

def get_blocks(partition):
    # This function assumes that partition has already been sorted by block id
    # (by 'value'). Find the block boundaries first:
    mask = np.ediff1d(partition['value'], to_begin=1, to_end=1)
    blx = np.flatnonzero(mask) # adjancent elements of blx give the block slices
    return blx

def check_block_ids(partition, block_count):
    # Assumption: block_ids is already sorted, and has block_count distinct
    # elements. Checking if distinct block_ids == 1:block_count:
    block_ids = partition['value']
    assertEqual(block_ids[ 0], 1)
    assertEqual(block_ids[-1], block_count)

def set_inverse_permutations(bsp):
    bsp.inverse_row_perm = util.invert_permutation(bsp.row_permutation)
    bsp.inverse_col_perm = util.invert_permutation(bsp.col_permutation)

def dbg_show(partition, permutation, blocks):
    print('permutation:', permutation)
    print('blocks:')
    for i, block_slice in itr_index_block_slice(blocks):
        print(i, partition['value'][block_slice])
    print('indices by block:')
    for i, block_slice in itr_index_block_slice(blocks):
        print(i, permutation[block_slice])

################################################################################
# FIXME This is a temporary hack to test block sparsity pattern

def get_permuted_block_profiles(bsp):
    blk_mat = sp.dok_matrix((len(bsp.rblx)-1,len(bsp.cblx)-1), dtype=np.int32)
    # Same logic as in nonzero plotting
    for i, j in csr_utils.itr_nonzero_indices(bsp.csr_mat):
        r, c = bsp.inverse_row_perm[i], bsp.inverse_col_perm[j]
        rblk = np.searchsorted(bsp.rblx, r, side='right') - 1 # inefficient, doesn't move within a row
        cblk = np.searchsorted(bsp.cblx, c, side='right') - 1
        key = (rblk,cblk)
        #print('i=%d j=%d r=%d c=%d rb=%d cb=%d' % (i,j,r,c,rblk,cblk))
        blk_mat[key] = blk_mat.get(key) + 1
    print('Block pattern:\n%s' % blk_mat.todense())
    rprof = get_row_profile(blk_mat)
    cprof = get_col_profile(blk_mat)
    return rprof, cprof

def get_row_profile(blk_mat):
    col_major = blk_mat.tocsc()
    col_major.sort_indices()
    rprof = util.indices_of_first_nonzeros(col_major)
    assert_in_lower_triangular_form(rprof, 'row')
    return rprof

def get_col_profile(blk_mat):
    row_major = blk_mat.tocsr()
    row_major.sort_indices()
    cprof = util.indices_of_last_nonzeros(row_major)
    assert_in_lower_triangular_form(cprof, 'col')
    return cprof

def assert_in_lower_triangular_form(prof, row_or_col):
    diff = np.ediff1d(prof)
    assert np.all(diff == 1) and (prof[0]==0), \
    '%s block profile not in lower triangular form:\n%s' % (row_or_col, prof)

################################################################################

def set_min_degree_order(bsp):
    # TODO Check if LTF
    m = bsp.csr_mat
    for i in xrange(len(bsp.rblx)-1):
        # Apply minimum degree ordering to each block on the diagonal (i,i)
        min_degree_ordering(m, bsp.row_permutation, bsp.col_permutation,
                            *get_block_boundaries(bsp, i, i))
    set_inverse_permutations(bsp)

def make_one_big_fake_block(bsp):
    # The whole matrix is just one big block
    bsp.rblx = np.fromiter((0, bsp.nrows), dtype=np.int32)
    bsp.cblx = np.fromiter((0, bsp.ncols), dtype=np.int32)
    # The permutation is the identity
    bsp.row_permutation = np.arange(bsp.nrows, dtype=np.int32)
    bsp.col_permutation = np.arange(bsp.ncols, dtype=np.int32)
    # Now we can call minimum degree
    set_min_degree_order(bsp)
<commit_msg>Minor refactoring<commit_after>from __future__ import print_function
from future_builtins import zip
import numpy as np
import scipy.sparse as sp
import csr_utils
import misc_utils as util
from util.assert_helpers import assertEqual, assertEqLength
from ordering.minimum_degree import min_degree_ordering

DEBUG = True

# TODO Code gen for AD

class BlockSparsityPattern:
    def __init__(self, name, nrows, ncols, nzeros):
        self.name = name
        self.nrows = nrows
        self.ncols = ncols
        self.nzeros = nzeros
        self.row_names = None
        self.col_names = None
        self.row_suffixes = { } # suffix name -> np.array of (index, value)
        self.col_suffixes = { } # suffix name -> np.array of (index, value)
        # Sparsity pattern of the Jacobian in the CSR format
        self.csr_data    = np.zeros(nzeros,  dtype=np.float64)
        self.csr_indices = np.zeros(nzeros,  dtype=np.int32)
        self.csr_indptr  = np.zeros(nrows+1, dtype=np.int32)
        self.csr_mat     = None   # View of (data, indices, indptr) as a csr_mat
        self.col_len = None # redundant info, can be computed from Jacobian too
        # These below are only needed to build the Jacobian, factor them out?
        self.csr_pos     = int(0) # Counter needed to build the csr_mat
        self.dbg_prev_row= int(-1)# Check whether the J segments are ordered
        # The data below comes from block reconstruction
        self.row_permutation  = None # AMPL row indices in permuted order
        self.col_permutation  = None # AMPL col indices in permuted order
        self.inverse_row_perm = None # inverse of row_permutation
        self.inverse_col_perm = None # inverse of col_permutation
        # Block boundaries
        self.rblx = None # adjancent elements of blx give the block slices
        self.cblx = None # block beg end indices: zip(blx[:-1],blx[1:])
        # indices in the ith block = permutation[slice(beg, end)]
        # block count: len(blx)-1

def itr_index_block_slice(blx):
    for i, beg_end in enumerate(zip(blx[:-1],blx[1:])):
        yield i, slice(*beg_end)

def get_block_boundaries(bsp, i, j):
    return bsp.rblx[i], bsp.rblx[i+1], bsp.cblx[j], bsp.cblx[j+1]

def n_blocks(bsp):
    return n_rblx(bsp), n_cblx(bsp)

def n_rblx(bsp):
    return len(bsp.rblx)-1

def n_cblx(bsp):
    return len(bsp.cblx)-1

################################################################################
# partition: np.array of (index, value) pairs, where value is the block id,
# i.e. partition['index'] gives the indices, partition['values'] the block ids

def set_permutation_with_block_boundaries(bsp):
    blockid = 'blockid'
    if (blockid not in bsp.row_suffixes) or (blockid not in bsp.col_suffixes):
        make_one_big_fake_block(bsp)
        return
    row_partition = bsp.row_suffixes[blockid]
    col_partition = bsp.col_suffixes[blockid]
    bsp.row_permutation, bsp.rblx = reconstruct(row_partition)
    bsp.col_permutation, bsp.cblx = reconstruct(col_partition)
    if DEBUG:
        assertEqLength(bsp.rblx, bsp.cblx) # it is not necessary in the general case
        print('ROWS')
        dbg_show(row_partition, bsp.row_permutation, bsp.rblx)
        print('COLS')
        dbg_show(col_partition, bsp.col_permutation, bsp.cblx)
    set_min_degree_order(bsp)

def make_one_big_fake_block(bsp):
    print('WARNING: No row and/or col partitions,', end='')
    print('the Jacobian will be treated as a single big block!')
    # Setting the whole matrix to be one big block
    bsp.rblx = np.fromiter((0, bsp.nrows), dtype=np.int32)
    bsp.cblx = np.fromiter((0, bsp.ncols), dtype=np.int32)
    # The permutation is the identity
    bsp.row_permutation = np.arange(bsp.nrows, dtype=np.int32)
    bsp.col_permutation = np.arange(bsp.ncols, dtype=np.int32)
    # Now we can call minimum degree
    set_min_degree_order(bsp)

def reconstruct(partition):
    # Sorts partition in place by block ids
    # returns: tuple of permutation vector, block boundaries as (beg, end) tuple
    sort_by_block_id(partition)
    blx = get_blocks(partition)
    check_block_ids(partition, len(blx)-1)
    return partition['index'], blx

def sort_by_block_id(partition):
    # in place, stable sort by block id (by 'value')
    np.ndarray.sort(partition, kind='mergesort', order='value')

def get_blocks(partition):
    # This function assumes that partition has already been sorted by block id
    # (by 'value'). Find the block boundaries first:
    mask = np.ediff1d(partition['value'], to_begin=1, to_end=1)
    blx = np.flatnonzero(mask) # adjancent elements of blx give the block slices
    return blx

def check_block_ids(partition, block_count):
    # Assumption: block_ids is already sorted, and has block_count distinct
    # elements. Checking if distinct block_ids == 1:block_count:
    block_ids = partition['value']
    assertEqual(block_ids[ 0], 1)
    assertEqual(block_ids[-1], block_count)

def dbg_show(partition, permutation, blocks):
    print('permutation:', permutation)
    print('blocks:')
    for i, block_slice in itr_index_block_slice(blocks):
        print(i, partition['value'][block_slice])
    print('indices by block:')
    for i, block_slice in itr_index_block_slice(blocks):
        print(i, permutation[block_slice])

def set_min_degree_order(bsp):
    # Checking whether the blocks are in lower triangular form
    set_inverse_permutations(bsp)
    get_permuted_block_profiles(bsp) # profiles are currently ignored
    # Unpacking bsp
    m, row_p, col_p = bsp.csr_mat, bsp.row_permutation, bsp.col_permutation
    # Apply minimum degree ordering to each block on the diagonal (i,i)
    for i in xrange(n_rblx(bsp)):
        min_degree_ordering(m, row_p, col_p, *get_block_boundaries(bsp, i, i))
    set_inverse_permutations(bsp)

def set_inverse_permutations(bsp):
    bsp.inverse_row_perm = util.invert_permutation(bsp.row_permutation)
    bsp.inverse_col_perm = util.invert_permutation(bsp.col_permutation)

################################################################################
# The block attributes (rblx and cblx) and the functions working on them seem to
# be separated: move them into a separate class?

def get_permuted_block_profiles(bsp):
    # The idea was to get the row and column profiles but apparently the blocks
    # are in lower triangular form in all the test examples, so the profiles are
    # currently ignored; we only check if the blocks are really in lower
    # triangular form
    blk_mat = sp.dok_matrix(n_blocks(bsp), dtype=np.int32)
    # Same logic as in nonzero plotting
    for i, j in csr_utils.itr_nonzero_indices(bsp.csr_mat):
        r, c = bsp.inverse_row_perm[i], bsp.inverse_col_perm[j]
        rblk = np.searchsorted(bsp.rblx, r, side='right') - 1 # inefficient, doesn't move within a row
        cblk = np.searchsorted(bsp.cblx, c, side='right') - 1
        key = (rblk,cblk)
        #print('i=%d j=%d r=%d c=%d rb=%d cb=%d' % (i,j,r,c,rblk,cblk))
        blk_mat[key] = blk_mat.get(key) + 1
    print('Block pattern:\n%s' % blk_mat.todense())
    rprof = get_row_profile(blk_mat)
    cprof = get_col_profile(blk_mat)
    return rprof, cprof

def get_row_profile(blk_mat):
    col_major = blk_mat.tocsc()
    col_major.sort_indices()
    rprof = util.indices_of_first_nonzeros(col_major)
    assert_in_lower_triangular_form(rprof, 'row')
    return rprof

def get_col_profile(blk_mat):
    row_major = blk_mat.tocsr()
    row_major.sort_indices()
    cprof = util.indices_of_last_nonzeros(row_major)
    assert_in_lower_triangular_form(cprof, 'col')
    return cprof

def assert_in_lower_triangular_form(prof, row_or_col):
    diff = np.ediff1d(prof)
    assert np.all(diff == 1) and (prof[0]==0), \
    '%s block profile not in lower triangular form:\n%s' % (row_or_col, prof)
<|endoftext|>


<commit_before>#Copyright 2017 Google Inc. All rights reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Rule for downloading apt packages and tar them in a .tar file."""

def _generate_add_additional_repo_commands(ctx, additional_repos):
    return """printf "{repos}" >> /etc/apt/sources.list.d/{name}_repos.list""".format(
    name=ctx.attr.name,
    repos='\n'.join(additional_repos))

def _generate_download_commands(ctx, packages, additional_repos):
    return """#!/bin/bash
set -ex
{add_additional_repo_commands}
# Remove /var/lib/apt/lists/* in the base image. apt-get update -y command will create them.
rm -rf /var/lib/apt/lists/*
# Fetch Index
apt-get update -y
# Make partial dir
mkdir -p /tmp/install/./partial
# Install command
apt-get install --no-install-recommends -y -q -o Dir::Cache="/tmp/install" -o Dir::Cache::archives="." {packages} --download-only
# Tar command to only include all the *.deb files and ignore other directories placed in the cache dir.
tar -cpf {output}.tar --directory /tmp/install/. `cd /tmp/install/. && ls *.deb`""".format(
    output=ctx.attr.name,
    packages=' '.join(packages),
    add_additional_repo_commands=_generate_add_additional_repo_commands(ctx, additional_repos))

def _run_download_script(ctx, build_contents, image_tar, output_tar, output_script, image_id_extractor):
    contents = build_contents.replace(image_tar.short_path, image_tar.path)
    contents = contents.replace(output_tar.short_path, output_tar.path)
    # The paths for running within bazel build are different and hence replace short_path
    # by full path
    ctx.actions.write(
        output = output_script,
        content = contents,
    )

    ctx.actions.run(
        outputs = [output_tar],
        executable = output_script,
        inputs = [image_tar, image_id_extractor],
    )

def _impl(ctx, image_tar=None, packages=None, additional_repos=None, output_executable=None, output_tar=None, output_script=None):
    """Implementation for the download_pkgs rule.

    Args:
        ctx: The bazel rule context
        image_tar: File, overrides ctx.file.image_tar
        packages: str List, overrides ctx.attr.packages
        additional_repos: str List, overrides ctx.attr.additional_repos
        output_executable: File, overrides ctx.outputs.executable
        output_tar: File, overrides ctx.outputs.pkg_tar
        output_script: File, overrides ctx.outputs.build_script
    """
    image_tar = image_tar or ctx.file.image_tar
    packages = packages or ctx.attr.packages
    additional_repos = additional_repos or ctx.attr.additional_repos
    output_executable = output_executable or ctx.outputs.executable
    output_tar = output_tar or ctx.outputs.pkg_tar
    output_script = output_script or ctx.outputs.build_script

    # Generate a shell script to run apt_get inside this docker image.
    # TODO(tejaldesai): Replace this by docker_run rule
    build_contents = """\
#!/bin/bash
set -ex

# Load the image and remember its name
image_id=$(python {image_id_extractor_path} {image_tar})
docker load -i {image_tar}

# Run the builder image.
cid=$(docker run -w="/" -d --privileged $image_id sh -c $'{download_commands}')
docker attach $cid
docker cp $cid:{installables}.tar {output}
# Cleanup
docker rm $cid
 """.format(image_tar=image_tar.short_path,
            installables=ctx.attr.name,
            download_commands=_generate_download_commands(ctx, packages, additional_repos),
            output=output_tar.short_path,
            image_id_extractor_path = ctx.file._image_id_extractor.path,
            )
    _run_download_script(ctx, build_contents, image_tar, output_tar, output_script, ctx.file._image_id_extractor)
    ctx.actions.write(
        output = output_executable,
        content = build_contents,
    )
    return struct(
        runfiles = ctx.runfiles(files = [image_tar, output_script, ctx.file._image_id_extractor]),
        files = depset([output_executable])
    )

_attrs = {
    "image_tar": attr.label(
        default = Label("//ubuntu:ubuntu_16_0_4_vanilla.tar"),
        allow_files = True,
        single_file = True,
    ),
    "packages": attr.string_list(
        mandatory = True,
    ),
    "additional_repos": attr.string_list(),
    "_image_id_extractor": attr.label(
        default = "@io_bazel_rules_docker//contrib:extract_image_id.py",
      allow_files = True,
      single_file = True,
    ),
}

_outputs = {
    "pkg_tar": "%{name}.tar",
    "build_script": "%{name}.sh",
}

# Export download_pkgs rule for other bazel rules to depend on.
download = struct(
    attrs = _attrs,
    outputs = _outputs,
    implementation = _impl,
)

"""Downloads packages within a container.

This rule creates a script to download packages within a container.
The script bunldes all the packages in a tarball.

Args:
  name: A unique name for this rule.
  image_tar: The image tar for the container used to download packages.
  packages: list of packages to download. e.g. ['curl', 'netbase']
  additional_repos: list of additional debian package repos to use, in sources.list format
"""

download_pkgs = rule(
    attrs = _attrs,
    executable = True,
    outputs = _outputs,
    implementation = _impl,
)
<commit_msg>Set epoch time for files when packing for determinism.<commit_after>#Copyright 2017 Google Inc. All rights reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Rule for downloading apt packages and tar them in a .tar file."""

def _generate_add_additional_repo_commands(ctx, additional_repos):
    return """printf "{repos}" >> /etc/apt/sources.list.d/{name}_repos.list""".format(
    name=ctx.attr.name,
    repos='\n'.join(additional_repos))

def _generate_download_commands(ctx, packages, additional_repos):
    return """#!/bin/bash
set -ex
{add_additional_repo_commands}
# Remove /var/lib/apt/lists/* in the base image. apt-get update -y command will create them.
rm -rf /var/lib/apt/lists/*
# Fetch Index
apt-get update -y
# Make partial dir
mkdir -p /tmp/install/./partial
# Install command
apt-get install --no-install-recommends -y -q -o Dir::Cache="/tmp/install" -o Dir::Cache::archives="." {packages} --download-only
# Tar command to only include all the *.deb files and ignore other directories placed in the cache dir.
tar -cpf {output}.tar --mtime='1970-01-01' --directory /tmp/install/. `cd /tmp/install/. && ls *.deb`""".format(
    output=ctx.attr.name,
    packages=' '.join(packages),
    add_additional_repo_commands=_generate_add_additional_repo_commands(ctx, additional_repos))

def _run_download_script(ctx, build_contents, image_tar, output_tar, output_script, image_id_extractor):
    contents = build_contents.replace(image_tar.short_path, image_tar.path)
    contents = contents.replace(output_tar.short_path, output_tar.path)
    # The paths for running within bazel build are different and hence replace short_path
    # by full path
    ctx.actions.write(
        output = output_script,
        content = contents,
    )

    ctx.actions.run(
        outputs = [output_tar],
        executable = output_script,
        inputs = [image_tar, image_id_extractor],
    )

def _impl(ctx, image_tar=None, packages=None, additional_repos=None, output_executable=None, output_tar=None, output_script=None):
    """Implementation for the download_pkgs rule.

    Args:
        ctx: The bazel rule context
        image_tar: File, overrides ctx.file.image_tar
        packages: str List, overrides ctx.attr.packages
        additional_repos: str List, overrides ctx.attr.additional_repos
        output_executable: File, overrides ctx.outputs.executable
        output_tar: File, overrides ctx.outputs.pkg_tar
        output_script: File, overrides ctx.outputs.build_script
    """
    image_tar = image_tar or ctx.file.image_tar
    packages = packages or ctx.attr.packages
    additional_repos = additional_repos or ctx.attr.additional_repos
    output_executable = output_executable or ctx.outputs.executable
    output_tar = output_tar or ctx.outputs.pkg_tar
    output_script = output_script or ctx.outputs.build_script

    # Generate a shell script to run apt_get inside this docker image.
    # TODO(tejaldesai): Replace this by docker_run rule
    build_contents = """\
#!/bin/bash
set -ex

# Load the image and remember its name
image_id=$(python {image_id_extractor_path} {image_tar})
docker load -i {image_tar}

# Run the builder image.
cid=$(docker run -w="/" -d --privileged $image_id sh -c $'{download_commands}')
docker attach $cid
docker cp $cid:{installables}.tar {output}
# Cleanup
docker rm $cid
 """.format(image_tar=image_tar.short_path,
            installables=ctx.attr.name,
            download_commands=_generate_download_commands(ctx, packages, additional_repos),
            output=output_tar.short_path,
            image_id_extractor_path = ctx.file._image_id_extractor.path,
            )
    _run_download_script(ctx, build_contents, image_tar, output_tar, output_script, ctx.file._image_id_extractor)
    ctx.actions.write(
        output = output_executable,
        content = build_contents,
    )
    return struct(
        runfiles = ctx.runfiles(files = [image_tar, output_script, ctx.file._image_id_extractor]),
        files = depset([output_executable])
    )

_attrs = {
    "image_tar": attr.label(
        default = Label("//ubuntu:ubuntu_16_0_4_vanilla.tar"),
        allow_files = True,
        single_file = True,
    ),
    "packages": attr.string_list(
        mandatory = True,
    ),
    "additional_repos": attr.string_list(),
    "_image_id_extractor": attr.label(
        default = "@io_bazel_rules_docker//contrib:extract_image_id.py",
      allow_files = True,
      single_file = True,
    ),
}

_outputs = {
    "pkg_tar": "%{name}.tar",
    "build_script": "%{name}.sh",
}

# Export download_pkgs rule for other bazel rules to depend on.
download = struct(
    attrs = _attrs,
    outputs = _outputs,
    implementation = _impl,
)

"""Downloads packages within a container.

This rule creates a script to download packages within a container.
The script bunldes all the packages in a tarball.

Args:
  name: A unique name for this rule.
  image_tar: The image tar for the container used to download packages.
  packages: list of packages to download. e.g. ['curl', 'netbase']
  additional_repos: list of additional debian package repos to use, in sources.list format
"""

download_pkgs = rule(
    attrs = _attrs,
    executable = True,
    outputs = _outputs,
    implementation = _impl,
)
<|endoftext|>


<commit_before>#!/usr/bin/env python
#
# @author:  Gunnar Schaefer, Kevin S. Hahn

import logging
import logging.config
log = logging.getLogger('internims')
logging.getLogger('requests').setLevel(logging.WARNING)

import re
import json
import base64
import datetime
import requests
import Crypto.Hash.SHA
import Crypto.PublicKey.RSA
import Crypto.Signature.PKCS1_v1_5

def update(db, api_uri, site_name, site_id, privkey, internims_url):
    """sends is-alive signal to internims central."""
    exp_userlist = [e['permissions'] for e in db.experiments.find(None, {'_id': False, 'permissions.uid': True})]
    col_userlist = [c['permissions'] for c in db.collections.find(None, {'_id': False, 'permissions.uid': True})]
    grp_userlist = [g['roles'] for g in db.groups.find(None, {'_id': False, 'roles.uid': True})]
    remote_users = list(set([user['uid'] for container in exp_userlist+col_userlist+grp_userlist for user in container if '#' in user['uid']]))

    payload = json.dumps({'site': site_id, 'api_uri': api_uri, 'users': remote_users, 'name': site_name})
    h = Crypto.Hash.SHA.new(payload)
    signature = Crypto.Signature.PKCS1_v1_5.new(privkey).sign(h)
    headers = {'Authorization': base64.b64encode(signature)}

    r = requests.post(internims_url, data=payload, headers=headers)
    if r.status_code == 200:
        response = (json.loads(r.content))
        # update remotes entries
        for site in response['sites']:
            site['timestamp'] = datetime.datetime.strptime(site['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ')
            db.remotes.update({'_id': site['_id']}, site, upsert=True)
        log.debug('updating remotes: ' + ', '.join((r['_id'] for r in response['sites'])))

        # delete remotes from users, who no longer have remotes
        db.users.update({'remotes': {'$exists': True}, '_id': {'$nin': response['users'].keys()}}, {'$unset': {'remotes': ''}}, multi=True)

        # add remotes to users
        log.debug('users w/ remotes: ' + ', '.join(response['users']))
        for uid, remotes in response['users'].iteritems():
            db.users.update({'_id': uid}, {'$set': {'remotes': remotes}})
        return True
    else:
        # r.reason contains generic description for the specific error code
        # need the part of the error response body that contains the detailed explanation
        reason = re.search('

\n(.*)\n\n\n \n', r.content)
        log.warning((r.status_code, reason.group(1)))
        return False

def clean_remotes(db):
    """removes db.remotes, and removes remotes field from all db.users"""
    log.debug('removing remotes from users, and remotes collection')
    db.remotes.remove({})
    db.users.update({'remotes': {'$exists': True}}, {'$unset': {'remotes': ''}}, multi=True)

if __name__ == '__main__':
    import os
    import sys
    import time
    import pymongo
    import argparse
    import ConfigParser

    arg_parser = argparse.ArgumentParser()
    arg_parser.add_argument('configfile',  help='path to configuration file')
    arg_parser.add_argument('--internims_url', help='https://internims.appspot.com')
    arg_parser.add_argument('--db_uri', help='DB URI')
    arg_parser.add_argument('--api_uri', help='API URL, without http:// or https://')
    arg_parser.add_argument('--site_id', help='instance ID')
    arg_parser.add_argument('--site_name', help='instance name')
    arg_parser.add_argument('--sleeptime', default=60, type=int, help='time to sleep between is alive signals')
    arg_parser.add_argument('--ssl_key', help='path to privkey file')
    args = arg_parser.parse_args()

    config = ConfigParser.ConfigParser({'here': os.path.dirname(os.path.abspath(args.configfile))})
    config.read(args.configfile)
    logging.config.fileConfig(args.configfile, disable_existing_loggers=False)

    privkey_file = args.ssl_key or config.get('nims', 'ssl_key')
    if privkey_file:
        try:
            privkey = Crypto.PublicKey.RSA.importKey(open(privkey_file).read())
        except Exception:
            log.warn(privkey_file + ' is not a valid private SSL key file, bailing out.')
            sys.exit(1)
        else:
            log.info('successfully loaded private SSL key from ' + privkey_file)
    else:
        log.warn('private SSL key not specified, bailing out.')
        sys.exit(1)

    db_uri = args.db_uri or config.get('nims', 'db_uri')
    db = (pymongo.MongoReplicaSetClient(db_uri) if 'replicaSet' in db_uri else pymongo.MongoClient(db_uri)).get_default_database()

    site_name = args.site_name or config.get('nims', 'site_name')
    site_id = args.site_id or config.get('nims', 'site_id')
    api_uri = args.api_uri or config.get('nims', 'api_uri')
    internims_url = args.internims_url or config.get('nims', 'internims_url')

    fail_count = 0

    while True:
        if not update(db, api_uri, site_name, site_id, privkey, internims_url):
            fail_count += 1
        else:
            fail_count = 0
        if fail_count == 3:
            log.debug('InterNIMS unreachable, purging all remotes info')
            clean_remotes(db)
        time.sleep(args.sleeptime)
<commit_msg>changes to remote user permissions in db<commit_after>#!/usr/bin/env python
#
# @author:  Gunnar Schaefer, Kevin S. Hahn

import logging
import logging.config
log = logging.getLogger('internims')
logging.getLogger('requests').setLevel(logging.WARNING)

import re
import json
import base64
import datetime
import requests
import Crypto.Hash.SHA
import Crypto.PublicKey.RSA
import Crypto.Signature.PKCS1_v1_5

def update(db, api_uri, site_name, site_id, privkey, internims_url):
    """sends is-alive signal to internims central."""
    exp_userlist = [e['permissions'] for e in db.experiments.find(None, {'_id': False, 'permissions.uid': True, 'permissions.site': True})]
    col_userlist = [c['permissions'] for c in db.collections.find(None, {'_id': False, 'permissions.uid': True, 'permissions.site': True})]
    grp_userlist = [g['roles'] for g in db.groups.find(None, {'_id': False, 'roles.uid': True, 'roles.site': True})]
    remote_users = list(set([user['uid']+'#'+user['site'] for container in exp_userlist+col_userlist+grp_userlist for user in container if user.get('site') != None]))

    payload = json.dumps({'site': site_id, 'api_uri': api_uri, 'users': remote_users, 'name': site_name})
    h = Crypto.Hash.SHA.new(payload)
    signature = Crypto.Signature.PKCS1_v1_5.new(privkey).sign(h)
    headers = {'Authorization': base64.b64encode(signature)}

    r = requests.post(internims_url, data=payload, headers=headers)
    if r.status_code == 200:
        response = (json.loads(r.content))
        # update remotes entries
        for site in response['sites']:
            site['timestamp'] = datetime.datetime.strptime(site['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ')
            db.remotes.update({'_id': site['_id']}, site, upsert=True)
        log.debug('updating remotes: ' + ', '.join((r['_id'] for r in response['sites'])))

        # delete remotes from users, who no longer have remotes
        db.users.update({'remotes': {'$exists': True}, '_id': {'$nin': response['users'].keys()}}, {'$unset': {'remotes': ''}}, multi=True)

        # add remotes to users
        log.debug('users w/ remotes: ' + ', '.join(response['users']))
        for uid, remotes in response['users'].iteritems():
            db.users.update({'_id': uid}, {'$set': {'remotes': remotes}})
        return True
    else:
        # r.reason contains generic description for the specific error code
        # need the part of the error response body that contains the detailed explanation
        reason = re.search('

\n(.*)\n\n\n \n', r.content)
        log.warning((r.status_code, reason.group(1)))
        return False

def clean_remotes(db):
    """removes db.remotes, and removes remotes field from all db.users"""
    log.debug('removing remotes from users, and remotes collection')
    db.remotes.remove({})
    db.users.update({'remotes': {'$exists': True}}, {'$unset': {'remotes': ''}}, multi=True)

if __name__ == '__main__':
    import os
    import sys
    import time
    import pymongo
    import argparse
    import ConfigParser

    arg_parser = argparse.ArgumentParser()
    arg_parser.add_argument('configfile',  help='path to configuration file')
    arg_parser.add_argument('--internims_url', help='https://internims.appspot.com')
    arg_parser.add_argument('--db_uri', help='DB URI')
    arg_parser.add_argument('--api_uri', help='API URL, without http:// or https://')
    arg_parser.add_argument('--site_id', help='instance ID')
    arg_parser.add_argument('--site_name', help='instance name')
    arg_parser.add_argument('--sleeptime', default=60, type=int, help='time to sleep between is alive signals')
    arg_parser.add_argument('--ssl_key', help='path to privkey file')
    args = arg_parser.parse_args()

    config = ConfigParser.ConfigParser({'here': os.path.dirname(os.path.abspath(args.configfile))})
    config.read(args.configfile)
    logging.config.fileConfig(args.configfile, disable_existing_loggers=False)

    privkey_file = args.ssl_key or config.get('nims', 'ssl_key')
    if privkey_file:
        try:
            privkey = Crypto.PublicKey.RSA.importKey(open(privkey_file).read())
        except Exception:
            log.warn(privkey_file + ' is not a valid private SSL key file, bailing out.')
            sys.exit(1)
        else:
            log.info('successfully loaded private SSL key from ' + privkey_file)
    else:
        log.warn('private SSL key not specified, bailing out.')
        sys.exit(1)

    db_uri = args.db_uri or config.get('nims', 'db_uri')
    db = (pymongo.MongoReplicaSetClient(db_uri) if 'replicaSet' in db_uri else pymongo.MongoClient(db_uri)).get_default_database()

    site_name = args.site_name or config.get('nims', 'site_name')
    site_id = args.site_id or config.get('nims', 'site_id')
    api_uri = args.api_uri or config.get('nims', 'api_uri')
    internims_url = args.internims_url or config.get('nims', 'internims_url')

    fail_count = 0

    while True:
        if not update(db, api_uri, site_name, site_id, privkey, internims_url):
            fail_count += 1
        else:
            fail_count = 0
        if fail_count == 3:
            log.debug('InterNIMS unreachable, purging all remotes info')
            clean_remotes(db)
        time.sleep(args.sleeptime)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2014, 2015 CERN.
#
# Invenio is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""Version information for Invenio.

This file is imported by ``invenio.__init__``, and parsed by ``setup.py``.
"""

# Respect the following format: major, minor, patch, ..., "dev"?, revision?
#
# - major, minor, patch are numbers starting at zero.
# - you can put as much sub version as you need before 'dev'
# - dev has to be set in development mode (non-release).
# - revision can be set if you want to override the date coming from git.
#
# See the doctest below.
version = (2, 0, 5)

def build_version(*args):
    """Build a PEP440 compatible version based on a list of arguments.

    Inspired by Django's django.utils.version

    .. doctest::

        >>> print(build_version(1, 0, 0))
        1.0.0
        >>> print(build_version(1, 1, 1))
        1.1.1
        >>> print(build_version(1, 2, 3, 4))
        1.2.3.4
        >>> print(build_version(2, 0, 0, 'dev', 1))
        2.0.0.dev1
        >>> print(build_version(2, 0, 0, 'dev'))  # doctest: +ELLIPSIS
        2.0.0.dev...
        >>> print(build_version(2, 0, 1, 'dev'))  # doctest: +ELLIPSIS
        2.0.1.dev...
        >>> print(build_version(1, 2, 3, 4, 5, 6, 'dev'))  # doctest: +ELLIPSIS
        1.2.3.4.5.6.dev...

    """
    if 'dev' in args:
        pos = args.index('dev')
    else:
        pos = len(args)

    def zero_search(acc, x):
        """Increment the counter until it stops seeing zeros."""
        position, searching = acc
        if searching:
            if x != 0:
                searching = False
            else:
                position += 1

        return (position, searching)

    last_zero = pos + 1 - reduce(zero_search, reversed(args[:pos]), (1, True))[0]
    parts = max(3, last_zero)
    version = '.'.join(str(arg) for arg in args[:parts])

    if len(args) > pos:
        revision = args[pos + 1] if len(args) > pos + 1 else git_revision()
        version += '.dev{0}'.format(revision)

    return version

def git_revision():
    """Get the timestamp of the latest git revision."""
    if not hasattr(git_revision, '_cache'):
        import datetime
        import subprocess
        call = subprocess.Popen(r'git log -1 --pretty=format:%ct --quiet HEAD',
                                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, shell=True)
        stdout, _ = call.communicate()
        try:
            timestamp = int(stdout.decode())
            ts = datetime.datetime.utcfromtimestamp(timestamp)
            revision = ts.strftime('%Y%m%d%H%M%S')
        except ValueError:
            revision = '0'

        git_revision._cache = revision

    return git_revision._cache

__version__ = build_version(*version)
<commit_msg>installation: post-release version bump<commit_after># -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2014, 2015 CERN.
#
# Invenio is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""Version information for Invenio.

This file is imported by ``invenio.__init__``, and parsed by ``setup.py``.
"""

# Respect the following format: major, minor, patch, ..., "dev"?, revision?
#
# - major, minor, patch are numbers starting at zero.
# - you can put as much sub version as you need before 'dev'
# - dev has to be set in development mode (non-release).
# - revision can be set if you want to override the date coming from git.
#
# See the doctest below.
version = (2, 0, 6, 'dev', 20150717)

def build_version(*args):
    """Build a PEP440 compatible version based on a list of arguments.

    Inspired by Django's django.utils.version

    .. doctest::

        >>> print(build_version(1, 0, 0))
        1.0.0
        >>> print(build_version(1, 1, 1))
        1.1.1
        >>> print(build_version(1, 2, 3, 4))
        1.2.3.4
        >>> print(build_version(2, 0, 0, 'dev', 1))
        2.0.0.dev1
        >>> print(build_version(2, 0, 0, 'dev'))  # doctest: +ELLIPSIS
        2.0.0.dev...
        >>> print(build_version(2, 0, 1, 'dev'))  # doctest: +ELLIPSIS
        2.0.1.dev...
        >>> print(build_version(1, 2, 3, 4, 5, 6, 'dev'))  # doctest: +ELLIPSIS
        1.2.3.4.5.6.dev...

    """
    if 'dev' in args:
        pos = args.index('dev')
    else:
        pos = len(args)

    def zero_search(acc, x):
        """Increment the counter until it stops seeing zeros."""
        position, searching = acc
        if searching:
            if x != 0:
                searching = False
            else:
                position += 1

        return (position, searching)

    last_zero = pos + 1 - reduce(zero_search, reversed(args[:pos]), (1, True))[0]
    parts = max(3, last_zero)
    version = '.'.join(str(arg) for arg in args[:parts])

    if len(args) > pos:
        revision = args[pos + 1] if len(args) > pos + 1 else git_revision()
        version += '.dev{0}'.format(revision)

    return version

def git_revision():
    """Get the timestamp of the latest git revision."""
    if not hasattr(git_revision, '_cache'):
        import datetime
        import subprocess
        call = subprocess.Popen(r'git log -1 --pretty=format:%ct --quiet HEAD',
                                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, shell=True)
        stdout, _ = call.communicate()
        try:
            timestamp = int(stdout.decode())
            ts = datetime.datetime.utcfromtimestamp(timestamp)
            revision = ts.strftime('%Y%m%d%H%M%S')
        except ValueError:
            revision = '0'

        git_revision._cache = revision

    return git_revision._cache

__version__ = build_version(*version)
<|endoftext|>


<commit_before>"""
Copyright (C) 2013 Michael Davidsaver
Licensed under AGPL 3+
See file "LICENSE" for full terms
"""

import tempfile, os

from django.test import TestCase

from models import Vendor, Part, Supply, Info

class Parts(TestCase):
    pass

class Vendors(TestCase):
#  def setUp(self):
#    self.client = Client()

    def test_create(self):
        R = self.client.get('/vendor/edit/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/vendor/edit/', {'name':'testa', 'site':'http://xyz.com/xyz'})
        self.assertRedirects(R, '/vendor/')

        M = Vendor.objects.get(name='testa')
        self.assertEqual(M.site,'http://xyz.com/xyz')

    def test_edit(self):
        M = Vendor.objects.create(name='testb', site='http://mmm.abc/nnn')

        R = self.client.get('/vendor/edit/testb/')
        self.assertEqual(R.status_code, 200)

        F = R.context['form']

        self.assertFalse(F.is_bound)
        self.assertFalse(F.is_valid())
        self.assertEqual(F['name'].value(), 'testb')
        self.assertEqual(F['site'].value(), 'http://mmm.abc/nnn')

        # Rename vendor
        R = self.client.post('/vendor/edit/testb/', {'name':'testc', 'site':'http://abc.xyz/a'})
        self.assertRedirects(R, '/vendor/')

        self.assertRaises(Vendor.DoesNotExist, Vendor.objects.get, name='testb')
        M = Vendor.objects.get(name='testc')
        self.assertEqual(M.site, 'http://abc.xyz/a')

    def test_delete(self):
        M = Vendor.objects.create(name='testd', site='http://mmm.abc/nnn')

        # Get confirmation page
        R = self.client.get('/vendor/delete/testd/')
        self.assertEqual(R.status_code, 200)

        N = Vendor.objects.get(name='testd')
        self.assertEqual(M, N)

        # Actually delete
        R = self.client.post('/vendor/delete/testd/')
        self.assertRedirects(R, '/vendor/')

        self.assertRaises(Vendor.DoesNotExist, Vendor.objects.get, name='testd')

class PartView(TestCase):
    def setUp(self):
        self.V = Vendor.objects.create(name='testv')

    def test_create(self):
        R = self.client.get('/part/edit/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/edit/', {'oem':self.V.pk,'partnum':'abcd','count':0,
                                             'desc':'test part'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/abcd/')

        R = self.client.get('/part/testv/abcd/')
        self.assertEqual(R.status_code, 200)

        R = self.client.get('/part/edit/testv/abcd/')
        self.assertEqual(R.status_code, 200)

    def test_delete(self):
        Part.objects.create(oem=self.V, partnum='xyz', desc='another part')

        R = self.client.get('/part/delete/testv/xyz/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/delete/testv/xyz/')
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/')

    def test_edit(self):
        Part.objects.create(oem=self.V, partnum='xyz', desc='another part')

        R = self.client.get('/part/edit/testv/xyz/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/edit/testv/xyz/',
                             {'oem':self.V.pk,'partnum':'other','count':42,
                              'desc':'another name'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/other/')

        self.assertRaises(Part.DoesNotExist, Part.objects.get, oem=self.V, partnum='xyz')

        M = Part.objects.get(oem=self.V, partnum='other')
        self.assertEqual(M.desc, 'another name')
        self.assertEqual(M.count, 42)

    def test_home(self):
        p = Part.objects.create(oem=self.V, partnum='xyz')
        R = self.client.get('')
        self.assertEqual(R.status_code, 200)
        self.assertEqual(list(R.context['object_list']), [p])

class SupplyView(TestCase):
    def setUp(self):
        self.V = Vendor.objects.create(name='testv')
        self.S = Vendor.objects.create(name='tests')
        self.P = Part.objects.create(oem=self.V, partnum='apart', desc='something')

    def test_create(self):
        R = self.client.get('/part/supply/add/testv/apart/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/supply/add/testv/apart/',
                             {'seller':self.S.pk,'partnum':'altnum','url':'http://tests.com'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

        S = Supply.objects.get(part=self.P, seller=self.S, partnum='altnum')
        self.assertEqual(S.partnum, 'altnum')

    def test_delete(self):
        S = Supply.objects.create(part=self.P, seller=self.S, partnum='altnum', url='http://tests.com')

        R = self.client.get('/part/supply/edit/testv/apart/tests/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/supply/delete/testv/apart/tests/')
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

        self.assertRaises(Supply.DoesNotExist, Supply.objects.get, part=self.P,
                          seller=self.S, partnum='altnum')

    def test_edit(self):
        S = Supply.objects.create(part=self.P, seller=self.S, partnum='altnum', url='http://tests.com')

        R = self.client.get('/part/supply/edit/testv/apart/tests/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/supply/edit/testv/apart/tests/',
                             {'seller':self.V.pk, 'partnum':'second', 'url':'http://tests.com'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

class InfoView(TestCase):
    def setUp(self):
        self.V = Vendor.objects.create(name='testv')
        self.P = Part.objects.create(oem=self.V, partnum='apart', desc='something')

    def test_info(self):
        R = self.client.post('/part/addinfo/testv/apart/',
                             {'desc':'aaa', 'url':'http://xyz.com/'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

        fp, fname = tempfile.mkstemp()
        fp = os.fdopen(fp, 'r+b')
        try:
            fp.write('test data')
            fp.seek(0)

            R = self.client.post('/part/addinfo/testv/apart/',
                                 {'desc':'bbb', 'url':'http://abc.com/','file':fp})
            self.assertEqual(R.status_code, 302)
            self.assertRedirects(R, '/part/testv/apart/')

        finally:
            fp.close()
            os.remove(fname)

        R = self.client.get('/part/testv/apart/')
        self.assertContains(R, 'http://xyz.com')

        O = Info.objects.filter(part=self.P)
        self.assertEqual(len(O), 2)

        O = Info.objects.get(part=self.P, desc='bbb')
        O.file.open()
        self.assertEqual(O.file.read(), 'test data')
        O.file.close()
        O.file.delete()
<commit_msg>test dec<commit_after>"""
Copyright (C) 2013 Michael Davidsaver
Licensed under AGPL 3+
See file "LICENSE" for full terms
"""

import tempfile, os

from django.test import TestCase

from models import Vendor, Part, Supply, Info

class Parts(TestCase):
    pass

class Vendors(TestCase):
#  def setUp(self):
#    self.client = Client()

    def test_create(self):
        R = self.client.get('/vendor/edit/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/vendor/edit/', {'name':'testa', 'site':'http://xyz.com/xyz'})
        self.assertRedirects(R, '/vendor/')

        M = Vendor.objects.get(name='testa')
        self.assertEqual(M.site,'http://xyz.com/xyz')

    def test_edit(self):
        M = Vendor.objects.create(name='testb', site='http://mmm.abc/nnn')

        R = self.client.get('/vendor/edit/testb/')
        self.assertEqual(R.status_code, 200)

        F = R.context['form']

        self.assertFalse(F.is_bound)
        self.assertFalse(F.is_valid())
        self.assertEqual(F['name'].value(), 'testb')
        self.assertEqual(F['site'].value(), 'http://mmm.abc/nnn')

        # Rename vendor
        R = self.client.post('/vendor/edit/testb/', {'name':'testc', 'site':'http://abc.xyz/a'})
        self.assertRedirects(R, '/vendor/')

        self.assertRaises(Vendor.DoesNotExist, Vendor.objects.get, name='testb')
        M = Vendor.objects.get(name='testc')
        self.assertEqual(M.site, 'http://abc.xyz/a')

    def test_delete(self):
        M = Vendor.objects.create(name='testd', site='http://mmm.abc/nnn')

        # Get confirmation page
        R = self.client.get('/vendor/delete/testd/')
        self.assertEqual(R.status_code, 200)

        N = Vendor.objects.get(name='testd')
        self.assertEqual(M, N)

        # Actually delete
        R = self.client.post('/vendor/delete/testd/')
        self.assertRedirects(R, '/vendor/')

        self.assertRaises(Vendor.DoesNotExist, Vendor.objects.get, name='testd')

class PartView(TestCase):
    def setUp(self):
        self.V = Vendor.objects.create(name='testv')

    def test_create(self):
        R = self.client.get('/part/edit/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/edit/', {'oem':self.V.pk,'partnum':'abcd','count':0,
                                             'desc':'test part'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/abcd/')

        R = self.client.get('/part/testv/abcd/')
        self.assertEqual(R.status_code, 200)

        R = self.client.get('/part/edit/testv/abcd/')
        self.assertEqual(R.status_code, 200)

    def test_delete(self):
        Part.objects.create(oem=self.V, partnum='xyz', desc='another part')

        R = self.client.get('/part/delete/testv/xyz/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/delete/testv/xyz/')
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/')

    def test_edit(self):
        Part.objects.create(oem=self.V, partnum='xyz', desc='another part')

        R = self.client.get('/part/edit/testv/xyz/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/edit/testv/xyz/',
                             {'oem':self.V.pk,'partnum':'other','count':42,
                              'desc':'another name'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/other/')

        self.assertRaises(Part.DoesNotExist, Part.objects.get, oem=self.V, partnum='xyz')

        M = Part.objects.get(oem=self.V, partnum='other')
        self.assertEqual(M.desc, 'another name')
        self.assertEqual(M.count, 42)

    def test_dec(self):
        P = Part.objects.create(oem=self.V, partnum='xyz', count=5, desc='another part')

        R = self.client.get('/part/use/testv/xyz/?next=/')
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/')

        self.assertEqual(Part.objects.get(pk=P.pk).count, 4)

        R = self.client.get('/part/use/testv/xyz/')
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/xyz/')

        self.assertEqual(Part.objects.get(pk=P.pk).count, 3)

    def test_home(self):
        p = Part.objects.create(oem=self.V, partnum='xyz')
        R = self.client.get('')
        self.assertEqual(R.status_code, 200)
        self.assertEqual(list(R.context['object_list']), [p])

class SupplyView(TestCase):
    def setUp(self):
        self.V = Vendor.objects.create(name='testv')
        self.S = Vendor.objects.create(name='tests')
        self.P = Part.objects.create(oem=self.V, partnum='apart', desc='something')

    def test_create(self):
        R = self.client.get('/part/supply/add/testv/apart/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/supply/add/testv/apart/',
                             {'seller':self.S.pk,'partnum':'altnum','url':'http://tests.com'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

        S = Supply.objects.get(part=self.P, seller=self.S, partnum='altnum')
        self.assertEqual(S.partnum, 'altnum')

    def test_delete(self):
        S = Supply.objects.create(part=self.P, seller=self.S, partnum='altnum', url='http://tests.com')

        R = self.client.get('/part/supply/edit/testv/apart/tests/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/supply/delete/testv/apart/tests/')
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

        self.assertRaises(Supply.DoesNotExist, Supply.objects.get, part=self.P,
                          seller=self.S, partnum='altnum')

    def test_edit(self):
        S = Supply.objects.create(part=self.P, seller=self.S, partnum='altnum', url='http://tests.com')

        R = self.client.get('/part/supply/edit/testv/apart/tests/')
        self.assertEqual(R.status_code, 200)

        R = self.client.post('/part/supply/edit/testv/apart/tests/',
                             {'seller':self.V.pk, 'partnum':'second', 'url':'http://tests.com'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

class InfoView(TestCase):
    def setUp(self):
        self.V = Vendor.objects.create(name='testv')
        self.P = Part.objects.create(oem=self.V, partnum='apart', desc='something')

    def test_info(self):
        R = self.client.post('/part/addinfo/testv/apart/',
                             {'desc':'aaa', 'url':'http://xyz.com/'})
        self.assertEqual(R.status_code, 302)
        self.assertRedirects(R, '/part/testv/apart/')

        fp, fname = tempfile.mkstemp()
        fp = os.fdopen(fp, 'r+b')
        try:
            fp.write('test data')
            fp.seek(0)

            R = self.client.post('/part/addinfo/testv/apart/',
                                 {'desc':'bbb', 'url':'http://abc.com/','file':fp})
            self.assertEqual(R.status_code, 302)
            self.assertRedirects(R, '/part/testv/apart/')

        finally:
            fp.close()
            os.remove(fname)

        R = self.client.get('/part/testv/apart/')
        self.assertContains(R, 'http://xyz.com')

        O = Info.objects.filter(part=self.P)
        self.assertEqual(len(O), 2)

        O = Info.objects.get(part=self.P, desc='bbb')
        O.file.open()
        self.assertEqual(O.file.read(), 'test data')
        O.file.close()
        O.file.delete()
<|endoftext|>


<commit_before>"""
TODO:
- Annotations, Comments etc..
- Optimize imports
- Add view for otherdevices (may be generalized with networkdevice)
- Add view for Licenses
"""
from flask import Flask, url_for, redirect, render_template, request, abort
from flask_admin.contrib import sqla
from flask_admin.contrib.sqla.validators import Unique
from flask_admin.model.form import InlineFormAdmin
from flask_security import current_user
from sqlalchemy.sql import func
from wtforms import ValidationError

try:
    from wtforms.validators import InputRequired
except ImportError:
    from wtforms.validators import Required as InputRequired
from inventory.models import db, User, Ip, Inventory, Location, Networkdevice, Networkdevicetype
# TODO: may be used for logging "from flask import current_app"

# Create customized model view class
class MyModelView(sqla.ModelView):
    def is_accessible(self):
        if not current_user.is_active or not current_user.is_authenticated:
            return False

        if current_user.has_role('superuser'):
            return True

        return False

    def _handle_view(self, name, **kwargs):
        """
        Override builtin _handle_view in order to redirect users when a view is not accessible.
        """
        if not self.is_accessible():
            if current_user.is_authenticated:
                # permission denied
                abort(403)
            else:
                # login
                return redirect(url_for('security.login', next=request.url))

class NetworkdeviceInlineModelForm(InlineFormAdmin):
    form_columns = ('id', 'networkname', 'networkdevicetype', 'mainboard', 'cpu',
                    'ram', "ram_details", 'description', 'ip', "description")
    column_labels = dict(networkname='Netzwerkname', mainboard="Modellbezeichnung/ Mainboard",
                         networkdevicetype='Geräte-Typ',
                         ram="Arbeitsspeicher", ram_details='Arbeitsspeicher Details')
    column_descriptions = dict(
        networkname="Name des Geräts im Netzwerk (gemäß VKM-Policy; Bsp.: Tanja2).",
        networkdevicetype="Art des netwerk-fähigen Gerätes.",
        mainboard="Je nach Typ Herstellerbezeichung/ Baureihe der Hardware (Bsp.: ASUS Z170-A | HP Aruba 2720 | KYOCERA FS1020D | Raspberry v3)",
        ram='Anzahl des zur Verfügung stehenden Arbeitsspeichers in Gigabytes (Bsp.: 8 )',
        ram_details='Format: [S0 bei Notebook-RAM],Chip,Modul,Weiteres (Bsp.: DDR3-2133,PC3-17000,ECC | S0,DDR4-3200,PC4L-25600,nur schnellster Riegel )'
    )

# #column_list = ( Systems.name)
# column_auto_select_related = True

# TODO: can be included from general helpers file; also make sure array index is accessible
class ItemsRequiredExactly(InputRequired):
    """
    A version of the ``InputRequired`` validator that works with relations,
    to require a minimum number of related items.
    """

    def __init__(self, min=1, message=None):
        """

        :param min:
        :param message:
        """
        super(ItemsRequiredExactly, self).__init__(message=message)
        self.min = min

    def __call__(self, form, field):
        """

        :param form:
        :param field:
        """
        if len(field.data) < self.min or len(field.data) > self.min:
            if self.message is None:
                message = field.ngettext(
                    u"Only %d item is allowed!",
                    u"Only least %d items are allowed!",
                    self.min
                ) % self.min
            else:
                message = self.message

            raise ValidationError(message)
        if field.data[0]["networkname"] == None:
            raise ValidationError(str(field.data[0]["networkname"]))

class InventoryNetworkDevicesView(MyModelView):
    inline_models = (NetworkdeviceInlineModelForm(Networkdevice),)
    can_delete = False
    can_export = True

    form_args = {
        "networkdevice": {  # "default": [{"id":None}],
            "validators": [ItemsRequiredExactly()],
            "min_entries": 1,
            "max_entries": 1
        },
        # "name": {"validators": [InputRequired()]},
        "inventorynumber": {"validators": [InputRequired(), Unique(db.session, Inventory, Inventory.inventorynumber)]}
    }
    form_columns = (
        'networkdevice', 'inventorynumber', 'responsible', 'bought_at',
        'location', "active", "created_at"
    )
    column_labels = dict(
        inventorynumber="Inventarnummer", networkdevice='Netzwerfähiges Gerät', responsible='Verantwortlicher',
        bought_at="Gekauft am",
        location="Standort", active='Aktiv', created_at='Erstellt am'
    )
    column_descriptions = dict(
        inventorynumber="TODO", networkname='TODO', responsible='TODO',
        bought_at="TODO", location="TODO", active='TODO', created_at='TODO'
    )
    column_list = (
        #        "id",
        "ip",
        "networkname",
        "inventorynumber",
        "verantwortlicher",
        "typ",
        "Standort",
        "bought_at"
    )
    form_excluded_columns = (
        "created_at"
    )
    # TODO: needed? default sortable is problemativ becauese overriding column_list
    #  column_editable_list = ("id")
    # column_default_sort = (Systems.name)
    # TODO filter for ajax ip-requests
    '''     def create_form(self):
        return self._use_filtered_parent2(
            super(InventoryNetworkDevicesView, self).create_form()
        )

    def edit_form(self, obj):
        return self._use_filtered_parent(
            super(InventoryNetworkDevicesView, self).edit_form(obj)
        )

    def _use_filtered_parent(self, form):
        form.networkdevice[0].ip.query_factory = self._get_parent_list
        return form

    def _get_parent_list(self):
        return Ip.query.filter((Ip.networkdevice_id == None) | (Ip.networkdevice_id == Networkdevice.id)).all()

    def _use_filtered_parent2(self, form):
        form.networkdevice[0].ip.query_factory = self._get_parent_list2
        return form

    def _get_parent_list2(self):
        return Ip.query.filter(Ip.networkdevice_id == None).all()'''
    # TODO: Do an Ip-change History:
    '''def after_model_change(self, form, nd, is_created):
        current_app.logger.info("Form: "+str(form.networkdevice[0].ip.data.id))
        current_app.logger.info("Model: " + str(nd.networkdevice[0].ip.id))'''

    # TODO unicode or to str??
    def __unicode__(self):
        return self.name

    def get_query(self):
        return (
            self.session.query(
                Inventory.id.label("id"),
                (func.IF(Ip.internetaccess > 0, Ip.address, Ip.address + " (nur intern)")).label("ip"),
                Networkdevice.networkname.label("networkname"),
                Networkdevicetype.name.label("typ"),
                Inventory.inventorynumber.label("inventorynumber"),
                (User.email + " " + User.first_name).label("verantwortlicher"),
                # User.email.label("email"),
                (Location.building + " " + Location.room + " " + Location.description).label("Standort"),
                Inventory.bought_at.label("bought_at")
            )
                .outerjoin(Networkdevice).outerjoin(Networkdevicetype).outerjoin(Ip).outerjoin(User).outerjoin(Location)
        )

    # TODO: Add needed sortable colums (Bug current stable with ids!! --> upgrade flask_admin over ???)
    def scaffold_sortable_columns(self):
        return {'networkname': 'networkname',
                'inventorynumber': 'inventorynumber',
                'typ': 'typ'}

    # TODO: see one above
    def scaffold_list_columns(self):
        """

        :return:
        """
        return ['id',
                'inventorynumber',
                'typ',
                'networkname']

class OtherdeviceInlineModelForm(InlineFormAdmin):
    form_columns = ('id', 'networkname', 'networkdevicetype', 'mainboard', 'cpu',
                    'ram', "ram_details", 'description', 'ip', "description")
    column_labels = dict(networkname='Netzwerkname', mainboard="Modellbezeichnung/ Mainboard",
                         networkdevicetype='Geräte-Typ',
                         ram="Arbeitsspeicher", ram_details='Arbeitsspeicher Details')
    column_descriptions = dict(
        networkname="Name des Geräts im Netzwerk (gemäß VKM-Policy; Bsp.: Tanja2).",
        networkdevicetype="Art des netwerk-fähigen Gerätes.",
        mainboard="Je nach Typ Herstellerbezeichung/ Baureihe der Hardware (Bsp.: ASUS Z170-A | HP Aruba 2720 | KYOCERA FS1020D | Raspberry v3)",
        ram='Anzahl des zur Verfügung stehenden Arbeitsspeichers in Gigabytes (Bsp.: 8 )',
        ram_details='Format: [S0 bei Notebook-RAM],Chip,Modul,Weiteres (Bsp.: DDR3-2133,PC3-17000,ECC | S0,DDR4-3200,PC4L-25600,nur schnellster Riegel )'
    )

class IpAddressesView(MyModelView):
    # Variables for view
    can_export = True

    # Set flags for readonly views
    def __init__(self, *args, **kwargs):
        """

        :param args:
        :param kwargs:
        """
        super(IpAddressesView, self).__init__(*args, **kwargs)
        if self.endpoint != "ip":
            self.can_create = False
            self.can_delete = False
            self.can_edit = False

    def get_query(self):
        """

        :return:
        """
        if self.endpoint == "ip_free":
            return super(MyModelView, self).get_query().filter(Ip.networkdevice_id.is_(None))
        elif self.endpoint == "ip_notfree":
            return super(MyModelView, self).get_query().filter(Ip.networkdevice_id.isnot(None))
        else:
            return super(MyModelView, self).get_query()
<commit_msg>add deinventorize<commit_after>"""
TODO:
- Annotations, Comments etc..
- Optimize imports
- Add view for otherdevices (may be generalized with networkdevice)
- Add view for Licenses
"""
from flask import Flask, url_for, redirect, render_template, request, abort, flash
from flask_admin.contrib import sqla
from flask_admin.contrib.sqla.validators import Unique
from flask_admin.model.form import InlineFormAdmin
from flask_admin.actions import action

from flask_security import current_user
from sqlalchemy.sql import func
from wtforms import ValidationError
import gettext
try:
    from wtforms.validators import InputRequired
except ImportError:
    from wtforms.validators import Required as InputRequired
from inventory.models import db, User, Ip, Inventory, Location, Networkdevice, Networkdevicetype
# TODO: may be used for logging "from flask import current_app"

# Create customized model view class
class MyModelView(sqla.ModelView):
    def is_accessible(self):
        if not current_user.is_active or not current_user.is_authenticated:
            return False

        if current_user.has_role('superuser'):
            return True

        return False

    def _handle_view(self, name, **kwargs):
        """
        Override builtin _handle_view in order to redirect users when a view is not accessible.
        """
        if not self.is_accessible():
            if current_user.is_authenticated:
                # permission denied
                abort(403)
            else:
                # login
                return redirect(url_for('security.login', next=request.url))

class NetworkdeviceInlineModelForm(InlineFormAdmin):
    form_columns = ('id', 'networkname', 'networkdevicetype', 'mainboard', 'cpu',
                    'ram', "ram_details", 'description', 'ip', "description")
    column_labels = dict(networkname='Netzwerkname', mainboard="Modellbezeichnung/ Mainboard",
                         networkdevicetype='Geräte-Typ',
                         ram="Arbeitsspeicher", ram_details='Arbeitsspeicher Details')
    column_descriptions = dict(
        networkname="Name des Geräts im Netzwerk (gemäß VKM-Policy; Bsp.: Tanja2).",
        networkdevicetype="Art des netwerk-fähigen Gerätes.",
        mainboard="Je nach Typ Herstellerbezeichung/ Baureihe der Hardware (Bsp.: ASUS Z170-A | HP Aruba 2720 | KYOCERA FS1020D | Raspberry v3)",
        ram='Anzahl des zur Verfügung stehenden Arbeitsspeichers in Gigabytes (Bsp.: 8 )',
        ram_details='Format: [S0 bei Notebook-RAM],Chip,Modul,Weiteres (Bsp.: DDR3-2133,PC3-17000,ECC | S0,DDR4-3200,PC4L-25600,nur schnellster Riegel )'
    )

# #column_list = ( Systems.name)
# column_auto_select_related = True

# TODO: can be included from general helpers file; also make sure array index is accessible
class ItemsRequiredExactly(InputRequired):
    """
    A version of the ``InputRequired`` validator that works with relations,
    to require a minimum number of related items.
    """

    def __init__(self, min=1, message=None):
        """

        :param min:
        :param message:
        """
        super(ItemsRequiredExactly, self).__init__(message=message)
        self.min = min

    def __call__(self, form, field):
        """

        :param form:
        :param field:
        """
        if len(field.data) < self.min or len(field.data) > self.min:
            if self.message is None:
                message = field.ngettext(
                    u"Only %d item is allowed!",
                    u"Only least %d items are allowed!",
                    self.min
                ) % self.min
            else:
                message = self.message

            raise ValidationError(message)
        if field.data[0]["networkname"] == None:
            raise ValidationError(str(field.data[0]["networkname"]))

class InventoryNetworkDevicesView(MyModelView):
    inline_models = (NetworkdeviceInlineModelForm(Networkdevice),)
    can_delete = False
    can_export = True

    form_args = {
        "networkdevice": {  # "default": [{"id":None}],
            "validators": [ItemsRequiredExactly()],
            "min_entries": 1,
            "max_entries": 1
        },
        # "name": {"validators": [InputRequired()]},
        "inventorynumber": {"validators": [InputRequired(), Unique(db.session, Inventory, Inventory.inventorynumber)]}
    }
    form_columns = (
        'networkdevice', 'inventorynumber', 'responsible', 'bought_at',
        'location', "active", "created_at"
    )
    column_labels = dict(
        inventorynumber="Inventarnummer", networkdevice='Netzwerfähiges Gerät', responsible='Verantwortlicher',
        bought_at="Gekauft am",
        location="Standort", active='Aktiv', created_at='Erstellt am'
    )
    column_descriptions = dict(
        inventorynumber="TODO", networkname='TODO', responsible='TODO',
        bought_at="TODO", location="TODO", active='TODO', created_at='TODO'
    )
    column_list = (
        #        "id",
        "ip",
        "networkname",
        "inventorynumber",
        "verantwortlicher",
        "typ",
        "Standort",
        "bought_at"
    )
    form_excluded_columns = (
        "created_at"
    )
    # TODO: needed? default sortable is problemativ becauese overriding column_list
    #  column_editable_list = ("id")
    # column_default_sort = (Systems.name)
    # TODO filter for ajax ip-requests
    '''     def create_form(self):
        return self._use_filtered_parent2(
            super(InventoryNetworkDevicesView, self).create_form()
        )

    def edit_form(self, obj):
        return self._use_filtered_parent(
            super(InventoryNetworkDevicesView, self).edit_form(obj)
        )

    def _use_filtered_parent(self, form):
        form.networkdevice[0].ip.query_factory = self._get_parent_list
        return form

    def _get_parent_list(self):
        return Ip.query.filter((Ip.networkdevice_id == None) | (Ip.networkdevice_id == Networkdevice.id)).all()

    def _use_filtered_parent2(self, form):
        form.networkdevice[0].ip.query_factory = self._get_parent_list2
        return form

    def _get_parent_list2(self):
        return Ip.query.filter(Ip.networkdevice_id == None).all()'''
    # TODO: Do an Ip-change History:
    '''def after_model_change(self, form, nd, is_created):
        current_app.logger.info("Form: "+str(form.networkdevice[0].ip.data.id))
        current_app.logger.info("Model: " + str(nd.networkdevice[0].ip.id))'''

    # TODO unicode or to str??
    def __unicode__(self):
        return self.name

    @action('deinventorize', 'Deinventarisieren', 'Sollen die Geräte wirklich deinventarisiert werden?')
    def action_deinventorize(self, ids):
        if len(ids) != 1:
            flash("Geräte bitte nur einzeln deinventarisiern, um Fehler zu vermeiden."+str())
            pass
        qry = Inventory.query.filter(Inventory.id == ids[0])
        rows = qry.all()
        if len(rows) != 1:
            flash("Inventarisiertes Gerät mit id " + str(ids[0]) + " wurde nicht gefunden.")
            pass
        #TODO: Free IP,
        flash("Gerät mit Inventarnummer <" + str(rows[0].inventorynumber) + "> wurde deinventarisiert. ")
        pass

    def get_query(self):
        return (
            self.session.query(
                Inventory.id.label("id"),
                (func.IF(Ip.internetaccess > 0, Ip.address, Ip.address + " (nur intern)")).label("ip"),
                Networkdevice.networkname.label("networkname"),
                Networkdevicetype.name.label("typ"),
                Inventory.inventorynumber.label("inventorynumber"),
                (User.email + " " + User.first_name).label("verantwortlicher"),
                # User.email.label("email"),
                (Location.building + " " + Location.room + " " + Location.description).label("Standort"),
                Inventory.bought_at.label("bought_at")
            )
                .outerjoin(Networkdevice).outerjoin(Networkdevicetype).outerjoin(Ip).outerjoin(User).outerjoin(Location)
        )

    # TODO: Add needed sortable colums (Bug current stable with ids!! --> upgrade flask_admin over ???)
    def scaffold_sortable_columns(self):
        return {'networkname': 'networkname',
                'inventorynumber': 'inventorynumber',
                'typ': 'typ'}

    # TODO: see one above
    def scaffold_list_columns(self):
        """

        :return:
        """
        return ['id',
                'inventorynumber',
                'typ',
                'networkname']

class OtherdeviceInlineModelForm(InlineFormAdmin):
    form_columns = ('id', 'networkname', 'networkdevicetype', 'mainboard', 'cpu',
                    'ram', "ram_details", 'description', 'ip', "description")
    column_labels = dict(networkname='Netzwerkname', mainboard="Modellbezeichnung/ Mainboard",
                         networkdevicetype='Geräte-Typ',
                         ram="Arbeitsspeicher", ram_details='Arbeitsspeicher Details')
    column_descriptions = dict(
        networkname="Name des Geräts im Netzwerk (gemäß VKM-Policy; Bsp.: Tanja2).",
        networkdevicetype="Art des netwerk-fähigen Gerätes.",
        mainboard="Je nach Typ Herstellerbezeichung/ Baureihe der Hardware (Bsp.: ASUS Z170-A | HP Aruba 2720 | KYOCERA FS1020D | Raspberry v3)",
        ram='Anzahl des zur Verfügung stehenden Arbeitsspeichers in Gigabytes (Bsp.: 8 )',
        ram_details='Format: [S0 bei Notebook-RAM],Chip,Modul,Weiteres (Bsp.: DDR3-2133,PC3-17000,ECC | S0,DDR4-3200,PC4L-25600,nur schnellster Riegel )'
    )

class IpAddressesView(MyModelView):
    # Variables for view
    can_export = True

    # Set flags for readonly views
    def __init__(self, *args, **kwargs):
        """

        :param args:
        :param kwargs:
        """
        super(IpAddressesView, self).__init__(*args, **kwargs)
        if self.endpoint != "ip":
            self.can_create = False
            self.can_delete = False
            self.can_edit = False

    def get_query(self):
        """

        :return:
        """
        if self.endpoint == "ip_free":
            return super(MyModelView, self).get_query().filter(Ip.networkdevice_id == None)
        elif self.endpoint == "ip_notfree":
            return super(MyModelView, self).get_query().filter(Ip.networkdevice_id != None)
        else:
            return super(MyModelView, self).get_query()
<|endoftext|>