Merge pull request #156 from Nebucatnetzer/dev

Update master
This commit is contained in:
Andreas Zweili 2023-08-28 20:45:59 +02:00 committed by GitHub
commit c92d38bf62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1588 additions and 1510 deletions

View File

@ -1,4 +1,9 @@
[paths]
source = ${PROJECT_DIR-default .}/src/
[html]
directory = ${PROJECT_DIR-default .}/htmlcov
[run] [run]
data_file = ${PROJECT_DIR-default .}/.coverage
omit = omit =
*apps.py, *apps.py,
*migrations/*, *migrations/*,

7
.editorconfig Normal file
View File

@ -0,0 +1,7 @@
[*.sh]
indent_style = space
indent_size = 4
[.envrc]
indent_style = space
indent_size = 4

46
.envrc
View File

@ -1,2 +1,48 @@
use flake use flake
eval "$shellHook" eval "$shellHook"
layout_postgres() {
export PGDATA="$(direnv_layout_dir)/postgres"
export PGHOST="$PGDATA"
if [[ ! -d "$PGDATA" ]]; then
initdb
echo -e "listen_addresses = 'localhost'\nunix_socket_directories = '$PGHOST'" >>"$PGDATA/postgresql.conf"
echo "CREATE DATABASE django;" | postgres --single -E postgres
fi
}
layout postgres
layout_poetry() {
PYPROJECT_TOML="${PYPROJECT_TOML:-pyproject.toml}"
if [[ ! -f "$PYPROJECT_TOML" ]]; then
log_status "No pyproject.toml found. Executing \`poetry init\` to create a \`$PYPROJECT_TOML\` first."
poetry init
fi
if [[ -d ".venv" ]]; then
VIRTUAL_ENV="$(pwd)/.venv"
else
VIRTUAL_ENV=$(
poetry env info --path 2>/dev/null
true
)
fi
if [[ -z $VIRTUAL_ENV || ! -d $VIRTUAL_ENV ]]; then
log_status "No virtual environment exists. Executing \`poetry install\` to create one."
poetry install
VIRTUAL_ENV=$(poetry env info --path)
fi
PATH_add "$VIRTUAL_ENV/bin"
export POETRY_ACTIVE=1
export VIRTUAL_ENV
}
if ! has nix; then
layout poetry
fi
export PROJECT_DIR=$(pwd)
export WEBPORT=$(($RANDOM + 1100))
export PGPORT=$(($WEBPORT + 100))
watch_file "$PGDATA/postgresql.conf"

View File

@ -1,8 +0,0 @@
[flake8]
exclude =
*migrations*,
__init__.py,
*cache*,
venv/,
src/manage.py,
src/network_inventory/settings/*

View File

@ -10,8 +10,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v18 - uses: cachix/install-nix-action@v22
- uses: cachix/cachix-action@v12 - uses: DeterminateSystems/magic-nix-cache-action@main
with: with:
name: networkinventory name: networkinventory
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
@ -28,11 +28,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v18 - uses: cachix/install-nix-action@v22
- uses: cachix/cachix-action@v12 - uses: DeterminateSystems/magic-nix-cache-action@main
with:
name: networkinventory
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Buid container - name: Buid container
run: | run: |
nix build .#container nix build .#container

View File

@ -11,11 +11,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v18 - uses: cachix/install-nix-action@v22
- uses: cachix/cachix-action@v12 - uses: DeterminateSystems/magic-nix-cache-action@main
with:
name: networkinventory
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Test - name: Test
run: | run: nix develop --command bash -c "dev test"
nix flake check -L -j auto env:
PROJECT_DIR: ${{ github.workspace }}

2
.gitignore vendored
View File

@ -173,7 +173,7 @@ migrations/
.vscode/ .vscode/
.pytest_cache/ .pytest_cache/
htmlcov/ htmlcov/
.second_run .first_run
/src/static /src/static
.idea/ .idea/

2
Procfile Normal file
View File

@ -0,0 +1,2 @@
web: python ./src/manage.py runserver 0.0.0.0:$WEBPORT
db: postgres -p $PGPORT

View File

@ -14,26 +14,62 @@ inventory over my various servers and other network equipment.
## Development Setup ## Development Setup
There are two ways to work on this project. There is currently only one supported way to work with this repository. You
For the first one you will need to install the Nix package manager[^1]. will need a Linux system (WSL might work) onto wich you install the Nix package
Afterwards you can enter the development environment with `nix develop`. manager with Flakes enabled[^1] and direnv[^3]. Afterwards you can enter the
development environment with `direnv allow`.
For the other way you have to install poetry[^2] and then run `poetry shell` to [^1]: https://nixos.org/download.html
enter the virtual environment. [^3]: https://direnv.net/
After you've entered the development environment with either method you can
start the development server with `dev run`. This will start a PostgreSQL
database running and start the Django development server.
_It will prompt you for your sudo password because it opens port 8000 in your
firewall. This is because I sometimes develope from my iPad on my notebook and
with this tweak I can access the dev server running on my notebook._
You can then access the project in the browser under the FQDN of your
computer. E.g. `http://mypc.domain.local:8000`.
In case you want a fresh start or remove the project you can just remove the
`.direnv` directory at the root of the project. All the data of the PostgreSQL
database is stored there together with the symlinks to the Nix store.
In case you want to tweak something these are the applications use do build the
development environment:
- Nix package manager
- direnv
- overmind[^4]
The `dev` command is a simple BASH script called `dev.sh` at the root of the
project.
[^4]: https://github.com/DarthSim/overmind
Run the `dev` command without an argument to see all options.
> Why aren't you using Docker/containers for development.
_I think containers have their uses but developing with them is in my opinion a
pain in the ass. You just can't easily interact with the tools inside the
container and you have to hack around to get your editor working with it.
In addition they aren't fully reproducable. Nix solves all of these
problems. Overmind then comes into play to orchestrate the few tasks that are
required to get a development environment up an running._
**Manual way**
The manual way you have to install poetry[^2] and then run `poetry shell` to
enter the virtual environment. You will then need a local PostgreSQL server or
modify the settings so that you can use your prefered database.
Please note that I will only use and test the first method. Please note that I will only use and test the first method.
[^1]: https://nixos.org/download.html
[^2]: https://python-poetry.org [^2]: https://python-poetry.org
After you've entered the development environment with either method you can
start the server. With the nix version you can start it with `dev run`. With
poetry `./dev.sh run`. This will start a PostgreSQL database running inside a
docker container and start the Django development server. You can then access
it in the browser under the FQDN of your computer. E.g. `mypc.domain.local`.
Run the `dev` command without an argument to see all options.
## Environment Variables ## Environment Variables
To customise the application in the Docker container you can use environment To customise the application in the Docker container you can use environment

154
dev.sh
View File

@ -1,15 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
run () { # Helper functions not exposed to the user {
setup # Load example data
find . -name __pycache__ -o -name "*.pyc" -delete _init() {
sudo iptables -I INPUT -p tcp --dport 8000 -j ACCEPT python ./src/manage.py loaddata src/network_inventory.yaml
python ./src/manage.py runserver 0.0.0.0:8000
} }
setup () { # Setup the database
docker-compose -f docker-compose-development.yml up -d _setup() {
if [ -f .second_run ]; then overmind start -l db -D
if [ -f .direnv/first_run ]; then
sleep 2 sleep 2
python ./src/manage.py collectstatic --noinput python ./src/manage.py collectstatic --noinput
python ./src/manage.py makemigrations python ./src/manage.py makemigrations
@ -34,59 +34,129 @@ setup () {
python ./src/manage.py loaddata nets python ./src/manage.py loaddata nets
python ./src/manage.py loaddata softwares python ./src/manage.py loaddata softwares
python ./src/manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')" python ./src/manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')"
touch .second_run _init
touch .direnv/first_run
fi
overmind quit
sleep 2
}
_open_url() {
if [[ ! -z "${DEFAULT_BROWSER}" ]]; then
$DEFAULT_BROWSER $url
elif type explorer.exe &>/dev/null; then
explorer.exe $url
fi fi
} }
venv () { _create_url() {
nix build .#venv -o venv if [ -f /etc/wsl.conf ]; then
echo "http://localhost:$WEBPORT"
else
echo "http://$(hostname -f):$WEBPORT"
fi
} }
#}
docker (){ # Main tasks start
nix build && docker load < result && docker run --rm -ti network-inventory:latest declare -A tasks
declare -A descriptions
run() {
_setup
find . -name __pycache__ -o -name "*.pyc" -delete
url=$(_create_url)
sudo iptables -I INPUT -p tcp --dport $WEBPORT -j ACCEPT
overmind start -D
printf "\n---\n webserver: $url\n---\n"
_open_url $url
} }
descriptions["run"]="Start the webserver."
tasks["run"]=run
descriptions["start"]="Alias for run."
tasks["start"]=run
clean () { stop() {
docker-compose -f docker-compose-development.yml down -v overmind quit
}
descriptions["stop"]="Stop the webserver and DB."
tasks["stop"]=stop
venv() {
nix build .#venv -o .venv
}
descriptions["venv"]="Build a pseudo venv that editors like VS Code can use."
tasks["venv"]=venv
build-container() {
nix build && docker load <result
}
descriptions["build-container"]="Build and load OCI container."
tasks["build-container"]=build-container
clean() {
find . \( -name __pycache__ -o -name "*.pyc" \) -delete find . \( -name __pycache__ -o -name "*.pyc" \) -delete
rm -rf htmlcov/ rm -rf htmlcov/
rm -f */migrations/0*.py rm -f .direnv/first_run
rm .second_run rm -f src/*/migrations/0*.py
rm -rf .direnv/postgres/
} }
descriptions["clean"]="Reset the project to a fresh state including the database."
tasks["clean"]=clean
cleanall () { cleanall() {
clean git clean -xdf
docker-compose -f docker-compose-development.yml down -v --rmi local
rm -r .venv
} }
descriptions["cleanall"]="Completly remove any files which are not checked into git."
tasks["cleanall"]=cleanall
init () { debug() {
python ./src/manage.py loaddata network_inventory.yaml
}
debug () {
pytest --pdb --nomigrations --cov=. --cov-report=html ./src/ pytest --pdb --nomigrations --cov=. --cov-report=html ./src/
} }
descriptions["debug"]="Run the tests and drop into the debugger on failure."
tasks["debug"]=debug
test (){ lint() {
nix flake check echo "Running pylint"
pylint \
--rc-file="$PROJECT_DIR/pyproject.toml" \
-j 0 \
-E "$PROJECT_DIR/src"
echo "Running mypy"
mypy --config-file="$PROJECT_DIR/pyproject.toml" "$PROJECT_DIR/src"
} }
descriptions["lint"]="Run the linters against the src directory."
tasks["lint"]=lint
tasks=("clean" "cleanall" "debug" "docker" "run" "test" "venv") test() {
DJANGO_SETTINGS_MODULE=network_inventory.settings.ram_test pytest \
-nauto \
--nomigrations \
--cov-config="$PROJECT_DIR/.coveragerc" \
--cov-report=html \
"$PROJECT_DIR/src"
}
descriptions["test"]="Run the tests in the RAM DB and write a coverage report."
tasks["test"]=test
update() {
poetry update --lock
}
descriptions["update"]="Update the dependencies."
tasks["update"]=update
# only one task at a time # only one task at a time
if [ $# != 1 ]; then if [ $# != 1 ]; then
echo "usage: $0 <task_name>" printf "usage: dev <task_name>\n\n"
echo "All tasks: ${tasks[@]}" for task in "${!tasks[@]}"; do
echo "$task - ${descriptions[$task]}"
done
else
# Check if task is available
if [[ -v "tasks[$1]" ]]; then
${tasks["$1"]}
else
echo "Task not found."
fi
fi fi
case $1 in
"${tasks[0]}") clean;;
"${tasks[1]}") cleanall;;
"${tasks[2]}") debug;;
"${tasks[3]}") docker;;
"${tasks[4]}") run;;
"${tasks[5]}") test;;
"${tasks[6]}") venv;;
esac

View File

@ -1,15 +0,0 @@
version: '3'
volumes:
db_data:
services:
db:
image: postgres
environment:
- POSTGRES_DB=network_inventory
- POSTGRES_PASSWORD=password
volumes:
- db_data:/var/lib/postgresql/data/
ports:
- "5432:5432"

View File

@ -1,12 +1,15 @@
{ {
"nodes": { "nodes": {
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1687709756,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -16,12 +19,15 @@
} }
}, },
"flake-utils_2": { "flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1689068808,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -30,13 +36,34 @@
"type": "github" "type": "github"
} }
}, },
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688870561,
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1670242877, "lastModified": 1688918189,
"narHash": "sha256-jBLh7dRHnbfvPPA9znOC6oQfKrCPJ0El8Zoe0BqnCjQ=", "narHash": "sha256-f8ZlJ67LgEUDnN7ZsAyd1/Fyby1VdOXWg4XY/irSGrQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6e51c97f1c849efdfd4f3b78a4870e6aa2da4198", "rev": "408c0e8c15a1c9cf5c3226931b6f283c9867c484",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -49,16 +76,17 @@
"poetry2nix": { "poetry2nix": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_2",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1670326426, "lastModified": 1693051011,
"narHash": "sha256-I5IscrjGuCbvpFIRoiappUwBBOq8OODvGLkapnn/ECA=", "narHash": "sha256-HNbuVCS/Fnl1YZOjBk9/MlIem+wM8fvIzTH0CVQrLSQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "poetry2nix", "repo": "poetry2nix",
"rev": "293dd5c31167540193bf2b66cec636eecd1fc788", "rev": "5b3a5151cf212021ff8d424f215fb030e4ff2837",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -73,6 +101,36 @@
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix" "poetry2nix": "poetry2nix"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@ -1,8 +1,8 @@
{ {
description = "A Python API for various tools I use at work."; description = "A Python API for various tools I use at work.";
inputs = { inputs = {
nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = github:numtide/flake-utils; flake-utils.url = "github:numtide/flake-utils";
poetry2nix = { poetry2nix = {
url = "github:nix-community/poetry2nix"; url = "github:nix-community/poetry2nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -45,31 +45,19 @@
rec { rec {
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
buildInputs = [ buildInputs = [
pkgs.gnumake
pkgs.inventoryDevEnv pkgs.inventoryDevEnv
pkgs.poetry pkgs.poetry
pkgs.python310Packages.pip pkgs.python310Packages.pip
pkgs.overmind
pkgs.postgresql_15
(pkgs.writeScriptBin "dev" "${builtins.readFile ./dev.sh}") (pkgs.writeScriptBin "dev" "${builtins.readFile ./dev.sh}")
]; ];
PYTHON_KEYRING_BACKEND = "keyring.backends.fail.Keyring";
shellHook = '' shellHook = ''
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local export DJANGO_SETTINGS_MODULE=network_inventory.settings.local
''; '';
}; };
checks = { checks = {
lint = pkgs.stdenv.mkDerivation {
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
doCheck = true;
name = "lint";
src = ./.;
checkInputs = [ pkgs.inventoryDevEnv ];
checkPhase = ''
mkdir -p $out
flake8 . --count --show-source --statistics
'';
};
tests = pkgs.stdenv.mkDerivation { tests = pkgs.stdenv.mkDerivation {
dontPatch = true; dontPatch = true;
dontConfigure = true; dontConfigure = true;
@ -82,10 +70,10 @@
checkPhase = '' checkPhase = ''
mkdir -p $out mkdir -p $out
pytest --ds=network_inventory.settings.ram_test \ pytest --ds=network_inventory.settings.ram_test \
-nauto \ -nauto \
--nomigrations \ --nomigrations \
--cov=./src \ --cov=./src \
./src ./src
''; '';
}; };
}; };
@ -102,7 +90,7 @@
pkgs.coreutils pkgs.coreutils
inventory inventory
(pkgs.writeShellScriptBin "start-inventory" '' (pkgs.writeShellScriptBin "start-inventory" ''
if [ -f .second_run ]; then if [ -f .first_run ]; then
sleep 2 sleep 2
${pkgs.inventoryEnv}/bin/django-admin collectstatic --noinput ${pkgs.inventoryEnv}/bin/django-admin collectstatic --noinput
${pkgs.inventoryEnv}/bin/django-admin makemigrations ${pkgs.inventoryEnv}/bin/django-admin makemigrations
@ -127,7 +115,7 @@
${pkgs.inventoryEnv}/bin/django-admin loaddata nets ${pkgs.inventoryEnv}/bin/django-admin loaddata nets
${pkgs.inventoryEnv}/bin/django-admin loaddata softwares ${pkgs.inventoryEnv}/bin/django-admin loaddata softwares
${pkgs.inventoryEnv}/bin/django-admin shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')" ${pkgs.inventoryEnv}/bin/django-admin shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')"
touch .second_run touch .first_run
fi fi
${pkgs.inventoryEnv}/bin/gunicorn network_inventory.wsgi:application --reload --bind 0.0.0.0:8000 --workers 3 ${pkgs.inventoryEnv}/bin/gunicorn network_inventory.wsgi:application --reload --bind 0.0.0.0:8000 --workers 3
'') '')

2078
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,18 @@
[tool.black] [tool.pylint]
line-length = 79 max-line-length = 88
load-plugins = [
"pylint_django",
]
good-names = [
"pk",
"ip",
]
[tool.pylint."MESSAGES CONTROL"]
disable = [
"missing-function-docstring",
"missing-class-docstring",
]
[tool.poetry] [tool.poetry]
name = "network_inventory" name = "network_inventory"
@ -11,36 +24,73 @@ packages = [
{ include = "src" }, { include = "src" },
] ]
[tool.mypy]
exclude = [
"tests/",
]
plugins = ["mypy_django_plugin.main"]
mypy_path = "./src"
# Start off with these
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
# Getting these passing should be easy
strict_equality = true
strict_concatenate = true
# Strongly recommend enabling this one as soon as you can
#check_untyped_defs = true
# These shouldn't be too much additional work, but may be tricky to
# get passing if you use a lot of untyped libraries
#disallow_subclassing_any = true
#disallow_untyped_decorators = true
#disallow_any_generics = true
[tool.django-stubs]
django_settings_module = "network_inventory.settings.local"
[[tool.mypy.overrides]]
module = [
"nested_admin.*",
"django_tables2.*",
"floppyforms.*",
"django_filters.*",
"crispy_forms.*",
"mixer.*",
"guardian.*",
]
ignore_missing_imports = true
[tool.poetry.group.main.dependencies] [tool.poetry.group.main.dependencies]
python = "^3.9" python = "^3.9"
Django = "^4.1.3" Django = "^4.1.3"
django-crispy-forms = "^1.14.0" django-crispy-forms = "^1.14.0"
django-filter = "^22.1" django-filter = "^23.2"
django-floppyforms = "^1.9.0" django-floppyforms = "^1.9.0"
django-guardian = "^2.4.0" django-guardian = "^2.4.0"
django-htmx = "^1.13.0" django-htmx = "^1.13.0"
django-model-utils = "^4.2.0" django-model-utils = "^4.2.0"
django-nested-admin = "^4.0.2" django-nested-admin = "^4.0.2"
django-tables2 = "^2.4.1" django-tables2 = "^2.4.1,<2.6.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
psycopg2-binary = "^2.9.5" psycopg2-binary = "^2.9.5"
PyYAML = "^6.0" PyYAML = "^6.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
autopep8 = "^2.0.0"
black = "^22.10.0" black = "^22.10.0"
coverage = "^6.5.0" coverage = "^6.5.0"
flake8 = "^6.0.0"
jedi = "^0.18.2"
mixer = "^7.2.2" mixer = "^7.2.2"
pep8 = "^1.7.1"
pylint = "^2.15.8" pylint = "^2.15.8"
pytest = "^7.2.0" pytest = "^7.2.0"
pytest-cov = "^4.0.0" pytest-cov = "^4.0.0"
pytest-django = "^4.5.2" pytest-django = "^4.5.2"
pytest-xdist = "^3.1.0" pytest-xdist = "^3.1.0"
rope = "^1.5.1" python-lsp-server = "^1.7.3"
yapf = "^0.32.0" mypy = "^1.4.1"
django-stubs = "^4.2.3"
pylint-django = "^2.5.3"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View File

@ -11,8 +11,6 @@ def backup_view_permission(old_fuction):
if user.has_perm("customers.view_customer", backup.computer.customer): if user.has_perm("customers.view_customer", backup.computer.customer):
return old_fuction(request, pk) return old_fuction(request, pk)
else: else:
return HttpResponseForbidden( return HttpResponseForbidden("You're not allowed to access this device.")
"You're not allowed to access this device."
)
return new_function return new_function

View File

@ -20,18 +20,12 @@ class Backup(models.Model):
computer = models.ForeignKey( computer = models.ForeignKey(
Computer, related_name="source_computer", on_delete=models.CASCADE Computer, related_name="source_computer", on_delete=models.CASCADE
) )
method = models.ForeignKey( method = models.ForeignKey(BackupMethod, models.SET_NULL, blank=True, null=True)
BackupMethod, models.SET_NULL, blank=True, null=True software = models.ForeignKey(Software, models.SET_NULL, blank=True, null=True)
)
software = models.ForeignKey(
Software, models.SET_NULL, blank=True, null=True
)
source_path = models.CharField(max_length=200, blank=True) source_path = models.CharField(max_length=200, blank=True)
exec_time = models.TimeField(null=True, blank=True) exec_time = models.TimeField(null=True, blank=True)
exec_days = models.ManyToManyField(Weekday, blank=True) exec_days = models.ManyToManyField(Weekday, blank=True)
target_device = models.ManyToManyField( target_device = models.ManyToManyField(Computer, through="TargetDevice", blank=True)
Computer, through="TargetDevice", blank=True
)
class Meta: class Meta:
ordering = ["name"] ordering = ["name"]
@ -50,9 +44,7 @@ class Backup(models.Model):
class TargetDevice(models.Model): class TargetDevice(models.Model):
device = models.ForeignKey( device = models.ForeignKey(Computer, models.SET_NULL, blank=True, null=True)
Computer, models.SET_NULL, blank=True, null=True
)
backup = models.ForeignKey(Backup, on_delete=models.CASCADE) backup = models.ForeignKey(Backup, on_delete=models.CASCADE)
target_path = models.CharField(max_length=200, blank=True) target_path = models.CharField(max_length=200, blank=True)

View File

@ -53,9 +53,7 @@ def test_backup_detail_view_with_target_device(create_admin_user):
software=mixer.SELECT, software=mixer.SELECT,
method=mixer.SELECT, method=mixer.SELECT,
) )
mixer.blend( mixer.blend("backups.TargetDevice", device=target_computer, backup=mixer.SELECT)
"backups.TargetDevice", device=target_computer, backup=mixer.SELECT
)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/backup/" + str(backup.id) + "/") response = client.get("/backup/" + str(backup.id) + "/")
@ -79,9 +77,7 @@ def test_backup_detail_view_with_notification(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/backup/" + str(backup.id) + "/") response = client.get("/backup/" + str(backup.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, notification)
response, notification
)
def test_backup_detail_view_with_day_relation(create_admin_user): def test_backup_detail_view_with_day_relation(create_admin_user):

View File

@ -23,9 +23,7 @@ def test_customer_backup_table(create_admin_user):
computer = mixer.blend("computers.Computer", customer=customer) computer = mixer.blend("computers.Computer", customer=customer)
backup = mixer.blend("backups.Backup", computer=computer) backup = mixer.blend("backups.Backup", computer=computer)
response = client.get("/customer/" + str(customer.id) + "/backups/") response = client.get("/customer/" + str(customer.id) + "/backups/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, backup.name)
response, backup.name
)
def test_customer_backup_table_no_backup(create_admin_user): def test_customer_backup_table_no_backup(create_admin_user):

View File

@ -3,9 +3,7 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path( path("customer/<int:pk>/backups/", views.backups_table_view, name="backups"),
"customer/<int:pk>/backups/", views.backups_table_view, name="backups"
),
path("backup/<int:pk>/", views.backup_detail_view, name="backup"), path("backup/<int:pk>/", views.backup_detail_view, name="backup"),
path( path(
"create/backup-for-computer/<int:pk>/", "create/backup-for-computer/<int:pk>/",

View File

@ -47,7 +47,7 @@ def backup_detail_view(request, pk):
class BackupCreateView(LoginRequiredMixin, CreateView): class BackupCreateView(LoginRequiredMixin, CreateView):
model = Backup model = Backup
template_name = "backups/backup_create.html" template_name = "backups/backup_create.html"
fields = "__all__" fields = "__all__" # type: ignore
def get_success_url(self): def get_success_url(self):
return reverse("computer", args=(self.computer.pk,)) return reverse("computer", args=(self.computer.pk,))
@ -63,7 +63,7 @@ class BackupCreateView(LoginRequiredMixin, CreateView):
} }
class BackupDeleteView(LoginRequiredMixin, DeleteView): class BackupDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Backup model = Backup
template_name = "backups/backup_confirm_delete.html" template_name = "backups/backup_confirm_delete.html"
@ -71,7 +71,7 @@ class BackupDeleteView(LoginRequiredMixin, DeleteView):
return reverse("computer", args=(self.object.computer.pk,)) return reverse("computer", args=(self.object.computer.pk,))
class BackupDeleteFromTableView(LoginRequiredMixin, DeleteView): class BackupDeleteFromTableView(LoginRequiredMixin, DeleteView): # type: ignore
model = Backup model = Backup
template_name = "backups/backup_confirm_delete.html" template_name = "backups/backup_confirm_delete.html"

View File

@ -26,62 +26,64 @@ from .models import (
) )
class SoftwareInLine(nested_admin.NestedStackedInline): class SoftwareInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = ComputerSoftwareRelation model = ComputerSoftwareRelation
extra = 0 extra = 0
verbose_name_plural = "Software" verbose_name_plural = "Software"
class RamInLine(nested_admin.NestedStackedInline): class RamInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = ComputerRamRelation model = ComputerRamRelation
extra = 0 extra = 0
verbose_name_plural = "RAM Modules" verbose_name_plural = "RAM Modules"
class DiskInLine(nested_admin.NestedStackedInline): class DiskInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = ComputerDiskRelation model = ComputerDiskRelation
extra = 0 extra = 0
verbose_name_plural = "Disks" verbose_name_plural = "Disks"
class DisksInRaidInLine(nested_admin.NestedStackedInline): class DisksInRaidInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = DisksInRaid model = DisksInRaid
extra = 0 extra = 0
verbose_name_plural = "Disks in RAID" verbose_name_plural = "Disks in RAID"
class CpusInLine(nested_admin.NestedStackedInline): class CpusInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = ComputerCpuRelation model = ComputerCpuRelation
extra = 0 extra = 0
verbose_name_plural = "CPUs" verbose_name_plural = "CPUs"
class GpusInLine(nested_admin.NestedStackedInline): class GpusInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = ComputerGpuRelation model = ComputerGpuRelation
extra = 0 extra = 0
verbose_name_plural = "GPUs" verbose_name_plural = "GPUs"
class RaidInLine(nested_admin.NestedStackedInline): class RaidInLine(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = Raid model = Raid
extra = 0 extra = 0
verbose_name_plural = "RAID" verbose_name_plural = "RAID"
inlines = (DisksInRaidInLine,) inlines = (DisksInRaidInLine,)
class DeviceInNetInline(nested_admin.NestedStackedInline): class DeviceInNetInline(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = DeviceInNet model = DeviceInNet
extra = 0 extra = 0
verbose_name_plural = "Nets" verbose_name_plural = "Nets"
class LicenseWithComputerInLine(nested_admin.NestedStackedInline): class LicenseWithComputerInLine(
nested_admin.NestedStackedInline
): # pylint: disable=no-member
model = LicenseWithComputer model = LicenseWithComputer
extra = 0 extra = 0
verbose_name_plural = "Licenses" verbose_name_plural = "Licenses"
class ComputerAdmin(nested_admin.NestedModelAdmin): class ComputerAdmin(nested_admin.NestedModelAdmin): # pylint: disable=no-member
list_display = ("name", "host") list_display = ("name", "host")
inlines = ( inlines = (
SoftwareInLine, SoftwareInLine,

View File

@ -41,15 +41,9 @@ class ComputerUpdateForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(ComputerUpdateForm, self).__init__(*args, **kwargs) super(ComputerUpdateForm, self).__init__(*args, **kwargs)
customers = utils.objects_for_allowed_customers( customers = utils.objects_for_allowed_customers(Customer, user=request.user)
Customer, user=request.user locations = utils.objects_for_allowed_customers(Location, user=request.user)
) hosts = utils.objects_for_allowed_customers(Computer, user=request.user)
locations = utils.objects_for_allowed_customers(
Location, user=request.user
)
hosts = utils.objects_for_allowed_customers(
Computer, user=request.user
)
users = utils.objects_for_allowed_customers(User, user=request.user) users = utils.objects_for_allowed_customers(User, user=request.user)
self.fields["customer"].queryset = customers self.fields["customer"].queryset = customers
self.fields["location"].queryset = locations self.fields["location"].queryset = locations

View File

@ -23,12 +23,8 @@ class Computer(Device):
ram = models.ManyToManyField(Ram, through="ComputerRamRelation") ram = models.ManyToManyField(Ram, through="ComputerRamRelation")
gpu = models.ManyToManyField(Gpu, through="ComputerGpuRelation") gpu = models.ManyToManyField(Gpu, through="ComputerGpuRelation")
disks = models.ManyToManyField(Disk, through="ComputerDiskRelation") disks = models.ManyToManyField(Disk, through="ComputerDiskRelation")
software = models.ManyToManyField( software = models.ManyToManyField(Software, through="ComputerSoftwareRelation")
Software, through="ComputerSoftwareRelation" host = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE)
)
host = models.ForeignKey(
"self", null=True, blank=True, on_delete=models.CASCADE
)
allocated_space = models.IntegerField(null=True, blank=True) allocated_space = models.IntegerField(null=True, blank=True)
def __str__(self): def __str__(self):

View File

@ -12,9 +12,7 @@ class RaidType(Category):
class Raid(models.Model): class Raid(models.Model):
usable_space = models.IntegerField(blank=True, null=True) usable_space = models.IntegerField(blank=True, null=True)
raid_type = models.ForeignKey( raid_type = models.ForeignKey(RaidType, models.SET_NULL, blank=True, null=True)
RaidType, models.SET_NULL, blank=True, null=True
)
computer = models.ForeignKey(Computer, on_delete=models.CASCADE) computer = models.ForeignKey(Computer, on_delete=models.CASCADE)
def __str__(self): def __str__(self):

View File

@ -15,15 +15,11 @@ def test_computer_detail_view_not_logged_in():
def test_computer_detail_view(create_admin_user): def test_computer_detail_view(create_admin_user):
create_admin_user() create_admin_user()
computer = mixer.blend( computer = mixer.blend("computers.Computer", customer=mixer.SELECT, os=mixer.SELECT)
"computers.Computer", customer=mixer.SELECT, os=mixer.SELECT
)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/computer/" + str(computer.id) + "/") response = client.get("/computer/" + str(computer.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, computer)
response, computer
)
def test_computer_detail_view_not_found(create_admin_user): def test_computer_detail_view_not_found(create_admin_user):
@ -43,9 +39,7 @@ def test_computer_detail_view_ram_relation(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/computer/" + str(computer.id) + "/") response = client.get("/computer/" + str(computer.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, "RAM Modules:")
response, "RAM Modules:"
)
def test_computer_detail_view_raid_relation(create_admin_user): def test_computer_detail_view_raid_relation(create_admin_user):
@ -53,9 +47,7 @@ def test_computer_detail_view_raid_relation(create_admin_user):
computer = mixer.blend("computers.Computer", customer=mixer.SELECT) computer = mixer.blend("computers.Computer", customer=mixer.SELECT)
raid_type = mixer.blend("computers.RaidType") raid_type = mixer.blend("computers.RaidType")
disk = mixer.blend("computers.Disk") disk = mixer.blend("computers.Disk")
raid = mixer.blend( raid = mixer.blend("computers.Raid", computer=computer, raid_type=raid_type)
"computers.Raid", computer=computer, raid_type=raid_type
)
mixer.blend("computers.DisksInRaid", raid=raid, disk=disk) mixer.blend("computers.DisksInRaid", raid=raid, disk=disk)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")

View File

@ -12,9 +12,7 @@ def test_computer_create_form(create_admin_user):
fixture = create_admin_user() fixture = create_admin_user()
user = mixer.blend("core.InventoryUser", customer=fixture["customer"]) user = mixer.blend("core.InventoryUser", customer=fixture["customer"])
form = forms.ComputerCreateForm(user=user, data={}) form = forms.ComputerCreateForm(user=user, data={})
assert ( assert form.is_valid() is False, "Should be false because no data was given"
form.is_valid() is False
), "Should be false because no data was given"
data = {"name": "pharma-pc1", "customer": 3} data = {"name": "pharma-pc1", "customer": 3}
form = forms.ComputerCreateForm(user=user, data=data) form = forms.ComputerCreateForm(user=user, data=data)
@ -32,9 +30,7 @@ def test_computer_update_form(create_admin_user):
request = HttpRequest() request = HttpRequest()
request.user = fixture["admin"] request.user = fixture["admin"]
form = forms.ComputerUpdateForm(request, data={}) form = forms.ComputerUpdateForm(request, data={})
assert ( assert form.is_valid() is False, "Should be false because no data was given"
form.is_valid() is False
), "Should be false because no data was given"
data = {"name": "pharma-pc1", "customer": 20356} data = {"name": "pharma-pc1", "customer": 20356}
form = forms.ComputerUpdateForm(request, data=data) form = forms.ComputerUpdateForm(request, data=data)

View File

@ -27,6 +27,4 @@ def test_computer_list_view(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/computers/all/") response = client.get("/computers/all/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, computer)
response, computer
)

View File

@ -21,9 +21,7 @@ def test_customer_computer_table(create_admin_user):
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
computer = mixer.blend("computers.Computer", customer=mixer.SELECT) computer = mixer.blend("computers.Computer", customer=mixer.SELECT)
response = client.get("/customer/" + str(customer.id) + "/computers/") response = client.get("/customer/" + str(customer.id) + "/computers/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, computer)
response, computer
)
def test_customer_computer_table_no_computer(create_admin_user): def test_customer_computer_table_no_computer(create_admin_user):

View File

@ -41,9 +41,7 @@ from .tables import ComputersTable
@login_required @login_required
def computer_detail_view(request, pk): def computer_detail_view(request, pk):
device = utils.get_object_with_view_permission( device = utils.get_object_with_view_permission(Computer, user=request.user, pk=pk)
Computer, user=request.user, pk=pk
)
disks_relations = ComputerDiskRelation.objects.filter(computer=pk) disks_relations = ComputerDiskRelation.objects.filter(computer=pk)
warranty_relations = Warranty.objects.filter(device=pk) warranty_relations = Warranty.objects.filter(device=pk)
ram_relations = ComputerRamRelation.objects.filter(computer=pk) ram_relations = ComputerRamRelation.objects.filter(computer=pk)
@ -76,9 +74,7 @@ def computer_detail_view(request, pk):
@login_required @login_required
def computers_table_view(request, pk): def computers_table_view(request, pk):
table = ComputersTable( table = ComputersTable(
utils.get_objects_for_customer( utils.get_objects_for_customer(Computer, user=request.user, customer_pk=pk)
Computer, user=request.user, customer_pk=pk
)
) )
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
return render( return render(
@ -139,9 +135,7 @@ def computer_update_view(request, pk):
A view to create a customer. A view to create a customer.
""" """
template_name = "computers/computer_update.html" template_name = "computers/computer_update.html"
computer = utils.get_object_with_view_permission( computer = utils.get_object_with_view_permission(Computer, user=request.user, pk=pk)
Computer, user=request.user, pk=pk
)
if request.method == "POST": if request.method == "POST":
form = ComputerUpdateForm(request, request.POST, instance=computer) form = ComputerUpdateForm(request, request.POST, instance=computer)
if form.is_valid(): if form.is_valid():
@ -152,7 +146,7 @@ def computer_update_view(request, pk):
return TemplateResponse(request, template_name, {"form": form}) return TemplateResponse(request, template_name, {"form": form})
class ComputerDeleteView(LoginRequiredMixin, DeleteView): class ComputerDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Computer model = Computer
def get_success_url(self): def get_success_url(self):
@ -178,7 +172,7 @@ class ComputerRamRelationCreateView(LoginRequiredMixin, CreateView):
} }
class ComputerRamRelationDeleteView(LoginRequiredMixin, DeleteView): class ComputerRamRelationDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = ComputerRamRelation model = ComputerRamRelation
template_name = "computers/relation_confirm_delete.html" template_name = "computers/relation_confirm_delete.html"
@ -205,7 +199,7 @@ class ComputerCpuRelationCreateView(LoginRequiredMixin, CreateView):
} }
class ComputerCpuRelationDeleteView(LoginRequiredMixin, DeleteView): class ComputerCpuRelationDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = ComputerCpuRelation model = ComputerCpuRelation
template_name = "computers/relation_confirm_delete.html" template_name = "computers/relation_confirm_delete.html"
@ -232,7 +226,7 @@ class ComputerGpuRelationCreateView(LoginRequiredMixin, CreateView):
} }
class ComputerGpuRelationDeleteView(LoginRequiredMixin, DeleteView): class ComputerGpuRelationDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = ComputerGpuRelation model = ComputerGpuRelation
template_name = "computers/relation_confirm_delete.html" template_name = "computers/relation_confirm_delete.html"
@ -259,7 +253,7 @@ class ComputerDiskRelationCreateView(LoginRequiredMixin, CreateView):
} }
class ComputerDiskRelationDeleteView(LoginRequiredMixin, DeleteView): class ComputerDiskRelationDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = ComputerDiskRelation model = ComputerDiskRelation
template_name = "computers/relation_confirm_delete.html" template_name = "computers/relation_confirm_delete.html"
@ -286,7 +280,7 @@ class ComputerSoftwareRelationCreateView(LoginRequiredMixin, CreateView):
} }
class ComputerSoftwareRelationDeleteView(LoginRequiredMixin, DeleteView): class ComputerSoftwareRelationDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = ComputerSoftwareRelation model = ComputerSoftwareRelation
template_name = "computers/relation_confirm_delete.html" template_name = "computers/relation_confirm_delete.html"
@ -313,7 +307,7 @@ class RaidCreateView(LoginRequiredMixin, CreateView):
} }
class RaidDeleteView(LoginRequiredMixin, DeleteView): class RaidDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Raid model = Raid
template_name = "computers/relation_confirm_delete.html" template_name = "computers/relation_confirm_delete.html"

View File

@ -22,9 +22,7 @@ def django_db_setup(django_db_setup, django_db_blocker):
def create_admin_user(): def create_admin_user():
def _create_admin_user(): def _create_admin_user():
User = get_user_model() User = get_user_model()
admin = User.objects.create_user( admin = User.objects.create_user("pharma-admin", "admin@pharma.com", "password")
"pharma-admin", "admin@pharma.com", "password"
)
customer = mixer.blend("customers.Customer") customer = mixer.blend("customers.Customer")
group = Group.objects.create(name="Pharma Corp. Admin") group = Group.objects.create(name="Pharma Corp. Admin")
admin.groups.add(group) admin.groups.add(group)

View File

@ -15,9 +15,7 @@ def test_get_object_with_view_permission(create_admin_user):
fixture = create_admin_user() fixture = create_admin_user()
customer = fixture["customer"] customer = fixture["customer"]
admin = fixture["admin"] admin = fixture["admin"]
object = utils.get_object_with_view_permission( object = utils.get_object_with_view_permission(Customer, user=admin, pk=customer.id)
Customer, user=admin, pk=customer.id
)
assert object == customer assert object == customer
@ -26,9 +24,7 @@ def test_get_object_with_view_permission_device(create_admin_user):
customer = fixture["customer"] customer = fixture["customer"]
admin = fixture["admin"] admin = fixture["admin"]
device = mixer.blend(Device, customer=customer) device = mixer.blend(Device, customer=customer)
object = utils.get_object_with_view_permission( object = utils.get_object_with_view_permission(Device, user=admin, pk=device.id)
Device, user=admin, pk=device.id
)
assert object == device assert object == device
@ -37,9 +33,7 @@ def test_get_object_without_view_permission(create_admin_user):
customer = mixer.blend(Customer) customer = mixer.blend(Customer)
admin = fixture["admin"] admin = fixture["admin"]
with pytest.raises(Http404): with pytest.raises(Http404):
utils.get_object_with_view_permission( utils.get_object_with_view_permission(Customer, user=admin, pk=customer.id)
Customer, user=admin, pk=customer.id
)
def test_get_object_without_view_permission_device(create_admin_user): def test_get_object_without_view_permission_device(create_admin_user):

View File

@ -16,9 +16,7 @@ def test_get_objects_for_customer_with_customer(create_admin_user):
customer = fixture["customer"] customer = fixture["customer"]
admin = fixture["admin"] admin = fixture["admin"]
with pytest.raises(Exception): with pytest.raises(Exception):
utils.get_objects_for_customer( utils.get_objects_for_customer(Customer, user=admin, customer_pk=customer.id)
Customer, user=admin, customer_pk=customer.id
)
def test_get_objects_for_customer_device(create_admin_user): def test_get_objects_for_customer_device(create_admin_user):
@ -37,9 +35,7 @@ def test_get_all_objects_for_unallowed_customers(create_admin_user):
customer = mixer.blend(Customer) customer = mixer.blend(Customer)
admin = fixture["admin"] admin = fixture["admin"]
with pytest.raises(Http404): with pytest.raises(Http404):
utils.get_objects_for_customer( utils.get_objects_for_customer(Customer, user=admin, customer_pk=customer.id)
Customer, user=admin, customer_pk=customer.id
)
def test_get_all_objects_for_unallowed_customers_device(create_admin_user): def test_get_all_objects_for_unallowed_customers_device(create_admin_user):
@ -48,6 +44,4 @@ def test_get_all_objects_for_unallowed_customers_device(create_admin_user):
admin = fixture["admin"] admin = fixture["admin"]
mixer.blend(Device, customer=customer) mixer.blend(Device, customer=customer)
with pytest.raises(Http404): with pytest.raises(Http404):
utils.get_objects_for_customer( utils.get_objects_for_customer(Device, user=admin, customer_pk=customer.id)
Device, user=admin, customer_pk=customer.id
)

View File

@ -34,9 +34,7 @@ def _get_customers(user):
user : django.contrib.auth.models.User user : django.contrib.auth.models.User
""" """
return get_objects_for_user( return get_objects_for_user(user, "customers.view_customer", klass=Customer)
user, "customers.view_customer", klass=Customer
)
def get_object_with_view_permission(model, user=None, pk=None): def get_object_with_view_permission(model, user=None, pk=None):

View File

@ -10,8 +10,6 @@ def customer_view_permission(old_function):
if user.has_perm("customers.view_customer", customer): if user.has_perm("customers.view_customer", customer):
return old_function(request, pk) return old_function(request, pk)
else: else:
return HttpResponseForbidden( return HttpResponseForbidden("You're not allowed to access this page.")
"You're not allowed to access this page."
)
return new_function return new_function

View File

@ -43,4 +43,4 @@ class DummyLocation(models.Model):
location = models.ForeignKey(Location, on_delete=models.CASCADE) location = models.ForeignKey(Location, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return self.location return self.location.name

View File

@ -6,9 +6,7 @@ from core.tables import CoreTable
class CustomersTable(CoreTable): class CustomersTable(CoreTable):
name = tables.LinkColumn("customer", args=[A("pk")]) name = tables.LinkColumn("customer", args=[A("pk")])
nets = tables.LinkColumn( nets = tables.LinkColumn("nets", text="Nets", args=[A("pk")], orderable=False)
"nets", text="Nets", args=[A("pk")], orderable=False
)
computers = tables.LinkColumn( computers = tables.LinkColumn(
"computers", text="Computers", args=[A("pk")], orderable=False "computers", text="Computers", args=[A("pk")], orderable=False
) )
@ -21,12 +19,8 @@ class CustomersTable(CoreTable):
licenses = tables.LinkColumn( licenses = tables.LinkColumn(
"licenses", text="Licenses", args=[A("pk")], orderable=False "licenses", text="Licenses", args=[A("pk")], orderable=False
) )
users = tables.LinkColumn( users = tables.LinkColumn("users", text="Users", args=[A("pk")], orderable=False)
"users", text="Users", args=[A("pk")], orderable=False groups = tables.LinkColumn("groups", text="Groups", args=[A("pk")], orderable=False)
)
groups = tables.LinkColumn(
"groups", text="Groups", args=[A("pk")], orderable=False
)
project_manager = tables.Column(verbose_name="Project Manager") project_manager = tables.Column(verbose_name="Project Manager")
delete = tables.LinkColumn( delete = tables.LinkColumn(
"customer_delete", "customer_delete",

View File

@ -42,24 +42,12 @@ def test_customer_list_view(create_admin_user):
assert ( assert (
response.status_code == 200 response.status_code == 200
and helper.in_content(response, customer) and helper.in_content(response, customer)
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer.id) + "/nets/")
response, "/customer/" + str(customer.id) + "/nets/" and helper.in_content(response, "/customer/" + str(customer.id) + "/computers/")
) and helper.in_content(response, "/customer/" + str(customer.id) + "/devices/")
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer.id) + "/backups/")
response, "/customer/" + str(customer.id) + "/computers/" and helper.in_content(response, "/customer/" + str(customer.id) + "/licenses/")
) and helper.in_content(response, "/customer/" + str(customer.id) + "/users/")
and helper.in_content(
response, "/customer/" + str(customer.id) + "/devices/"
)
and helper.in_content(
response, "/customer/" + str(customer.id) + "/backups/"
)
and helper.in_content(
response, "/customer/" + str(customer.id) + "/licenses/"
)
and helper.in_content(
response, "/customer/" + str(customer.id) + "/users/"
)
and helper.in_content(response, project_manager) and helper.in_content(response, project_manager)
) )
@ -75,41 +63,21 @@ def test_customer_list_view_multiple_customers(create_admin_user):
assert ( assert (
response.status_code == 200 response.status_code == 200
and helper.in_content(response, customer1) and helper.in_content(response, customer1)
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer1.id) + "/nets/")
response, "/customer/" + str(customer1.id) + "/nets/"
)
and helper.in_content( and helper.in_content(
response, "/customer/" + str(customer1.id) + "/computers/" response, "/customer/" + str(customer1.id) + "/computers/"
) )
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer1.id) + "/devices/")
response, "/customer/" + str(customer1.id) + "/devices/" and helper.in_content(response, "/customer/" + str(customer1.id) + "/backups/")
) and helper.in_content(response, "/customer/" + str(customer1.id) + "/licenses/")
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer1.id) + "/users/")
response, "/customer/" + str(customer1.id) + "/backups/"
)
and helper.in_content(
response, "/customer/" + str(customer1.id) + "/licenses/"
)
and helper.in_content(
response, "/customer/" + str(customer1.id) + "/users/"
)
and helper.in_content(response, customer2) and helper.in_content(response, customer2)
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer2.id) + "/nets/")
response, "/customer/" + str(customer2.id) + "/nets/"
)
and helper.in_content( and helper.in_content(
response, "/customer/" + str(customer2.id) + "/computers/" response, "/customer/" + str(customer2.id) + "/computers/"
) )
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer2.id) + "/devices/")
response, "/customer/" + str(customer2.id) + "/devices/" and helper.in_content(response, "/customer/" + str(customer2.id) + "/backups/")
) and helper.in_content(response, "/customer/" + str(customer2.id) + "/licenses/")
and helper.in_content( and helper.in_content(response, "/customer/" + str(customer1.id) + "/users/")
response, "/customer/" + str(customer2.id) + "/backups/"
)
and helper.in_content(
response, "/customer/" + str(customer2.id) + "/licenses/"
)
and helper.in_content(
response, "/customer/" + str(customer1.id) + "/users/"
)
) )

View File

@ -10,9 +10,7 @@ def test_location_form(create_admin_user):
fixture = create_admin_user() fixture = create_admin_user()
user = fixture["admin"] user = fixture["admin"]
form = forms.LocationForm(user=user, data={}) form = forms.LocationForm(user=user, data={})
assert ( assert form.is_valid() is False, "Should be false because no data was given"
form.is_valid() is False
), "Should be false because no data was given"
data = {"name": "Main Office", "customer": 3} data = {"name": "Main Office", "customer": 3}
form = forms.LocationForm(user=user, data=data) form = forms.LocationForm(user=user, data=data)

View File

@ -14,9 +14,7 @@ def test_load_htmx_create_location_view(create_admin_user):
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
url = "/create/location/" url = "/create/location/"
response = client.get(url) response = client.get(url)
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, "Add Location")
response, "Add Location"
)
def test_htmx_create_location_view(create_admin_user): def test_htmx_create_location_view(create_admin_user):
@ -25,9 +23,7 @@ def test_htmx_create_location_view(create_admin_user):
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
data = {"name": mixer.faker.name(), "save_location": 1} data = {"name": mixer.faker.name(), "save_location": 1}
response = client.post("/create/location/", data) response = client.post("/create/location/", data)
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, data["name"])
response, data["name"]
)
def test_htmx_create_location_view_invalid_form(create_admin_user): def test_htmx_create_location_view_invalid_form(create_admin_user):

View File

@ -24,9 +24,7 @@ def customers_table_view(request):
customers = utils.objects_for_allowed_customers(Customer, request.user) customers = utils.objects_for_allowed_customers(Customer, request.user)
table = CustomersTable(customers) table = CustomersTable(customers)
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
return render( return render(request, "customers/customer_list.html", {"customers": table})
request, "customers/customer_list.html", {"customers": table}
)
@login_required @login_required
@ -44,23 +42,17 @@ def create_customer(request):
) )
form = CustomerForm() form = CustomerForm()
context = {"form": form} context = {"form": form}
return TemplateResponse( return TemplateResponse(request, "customers/partials/customer_create.html", context)
request, "customers/partials/customer_create.html", context
)
@login_required @login_required
def customer_detail_view(request, pk): def customer_detail_view(request, pk):
customer = utils.get_object_with_view_permission( customer = utils.get_object_with_view_permission(Customer, user=request.user, pk=pk)
Customer, user=request.user, pk=pk
)
context = {"customer": customer} context = {"customer": customer}
return TemplateResponse( return TemplateResponse(request, "customers/customer_details.html", context)
request, "customers/customer_details.html", context
)
class CustomerDeleteView(LoginRequiredMixin, DeleteView): class CustomerDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Customer model = Customer
def get_success_url(self): def get_success_url(self):

View File

@ -12,13 +12,13 @@ from .models import (
) )
class DeviceInNetInline(nested_admin.NestedStackedInline): class DeviceInNetInline(nested_admin.NestedStackedInline): # pylint: disable=no-member
model = DeviceInNet model = DeviceInNet
extra = 0 extra = 0
verbose_name_plural = "Nets" verbose_name_plural = "Nets"
class DeviceAdmin(nested_admin.NestedModelAdmin): class DeviceAdmin(nested_admin.NestedModelAdmin): # pylint: disable=no-member
inlines = (DeviceInNetInline,) inlines = (DeviceInNetInline,)

View File

@ -11,8 +11,6 @@ def device_view_permission(old_function):
if user.has_perm("customers.view_customer", device.customer): if user.has_perm("customers.view_customer", device.customer):
return old_function(request, pk) return old_function(request, pk)
else: else:
return HttpResponseForbidden( return HttpResponseForbidden("You're not allowed to access this device.")
"You're not allowed to access this device."
)
return new_function return new_function

View File

@ -69,12 +69,8 @@ class DeviceUpdateForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(DeviceUpdateForm, self).__init__(*args, **kwargs) super(DeviceUpdateForm, self).__init__(*args, **kwargs)
customers = utils.objects_for_allowed_customers( customers = utils.objects_for_allowed_customers(Customer, user=request.user)
Customer, user=request.user locations = utils.objects_for_allowed_customers(Location, user=request.user)
)
locations = utils.objects_for_allowed_customers(
Location, user=request.user
)
users = utils.objects_for_allowed_customers(User, user=request.user) users = utils.objects_for_allowed_customers(User, user=request.user)
self.fields["customer"].queryset = customers self.fields["customer"].queryset = customers
self.fields["location"].queryset = locations self.fields["location"].queryset = locations

View File

@ -38,9 +38,7 @@ class DeviceCategory(Category):
class HardwareModel(models.Model): class HardwareModel(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(DeviceManufacturer, on_delete=models.CASCADE)
DeviceManufacturer, on_delete=models.CASCADE
)
class Meta: class Meta:
ordering = ["name"] ordering = ["name"]
@ -56,19 +54,13 @@ class Device(models.Model):
category = models.ForeignKey( category = models.ForeignKey(
DeviceCategory, on_delete=models.SET_NULL, null=True, blank=True DeviceCategory, on_delete=models.SET_NULL, null=True, blank=True
) )
owner = models.ForeignKey( owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, null=True, blank=True)
Owner, on_delete=models.SET_NULL, null=True, blank=True
)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE) customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
DeviceManufacturer, models.SET_NULL, null=True, blank=True DeviceManufacturer, models.SET_NULL, null=True, blank=True
) )
model = models.ForeignKey( model = models.ForeignKey(HardwareModel, models.SET_NULL, null=True, blank=True)
HardwareModel, models.SET_NULL, null=True, blank=True location = models.ForeignKey(Location, models.SET_NULL, null=True, blank=True)
)
location = models.ForeignKey(
Location, models.SET_NULL, null=True, blank=True
)
user = models.ForeignKey(User, models.SET_NULL, null=True, blank=True) user = models.ForeignKey(User, models.SET_NULL, null=True, blank=True)
installation_date = models.DateField(null=True, blank=True) installation_date = models.DateField(null=True, blank=True)
net = models.ManyToManyField(Net, through="DeviceInNet") net = models.ManyToManyField(Net, through="DeviceInNet")

View File

@ -16,9 +16,7 @@ class WarrantyType(Category):
class Warranty(models.Model): class Warranty(models.Model):
customer = models.ForeignKey( customer = models.ForeignKey(Customer, on_delete=models.CASCADE, blank=True)
Customer, on_delete=models.CASCADE, blank=True
)
device = models.ForeignKey(Device, on_delete=models.CASCADE) device = models.ForeignKey(Device, on_delete=models.CASCADE)
valid_from = models.DateField() valid_from = models.DateField()
valid_until = models.DateField() valid_until = models.DateField()

View File

@ -12,9 +12,7 @@ def test_device_create_form(create_admin_user):
fixture = create_admin_user() fixture = create_admin_user()
user = mixer.blend("core.InventoryUser", customer=fixture["customer"]) user = mixer.blend("core.InventoryUser", customer=fixture["customer"])
form = forms.DeviceCreateForm(user=user, data={}) form = forms.DeviceCreateForm(user=user, data={})
assert ( assert form.is_valid() is False, "Should be false because no data was given"
form.is_valid() is False
), "Should be false because no data was given"
data = {"name": "pharma-device1", "customer": 3} data = {"name": "pharma-device1", "customer": 3}
form = forms.DeviceCreateForm(user=user, data=data) form = forms.DeviceCreateForm(user=user, data=data)
@ -32,9 +30,7 @@ def test_device_update_form(create_admin_user):
request = HttpRequest() request = HttpRequest()
request.user = fixture["admin"] request.user = fixture["admin"]
form = forms.DeviceUpdateForm(request, data={}) form = forms.DeviceUpdateForm(request, data={})
assert ( assert form.is_valid() is False, "Should be false because no data was given"
form.is_valid() is False
), "Should be false because no data was given"
data = {"name": "pharma-device1", "customer": 3} data = {"name": "pharma-device1", "customer": 3}
form = forms.DeviceUpdateForm(request, data=data) form = forms.DeviceUpdateForm(request, data=data)
@ -50,9 +46,7 @@ def test_device_update_form(create_admin_user):
def test_device_create_form_duplicate_device(create_admin_user): def test_device_create_form_duplicate_device(create_admin_user):
fixture = create_admin_user() fixture = create_admin_user()
user = mixer.blend("core.InventoryUser", customer=fixture["customer"]) user = mixer.blend("core.InventoryUser", customer=fixture["customer"])
mixer.blend( mixer.blend("devices.Device", name="pharma-device1", customer=fixture["customer"])
"devices.Device", name="pharma-device1", customer=fixture["customer"]
)
data = {"name": "pharma-device1", "customer": fixture["customer"].id} data = {"name": "pharma-device1", "customer": fixture["customer"].id}
form = forms.DeviceCreateForm(user=user, data=data) form = forms.DeviceCreateForm(user=user, data=data)
assert ( assert (

View File

@ -35,9 +35,7 @@ def test_load_device_update_view(create_admin_user):
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
device = mixer.blend("devices.Device", customer=mixer.SELECT) device = mixer.blend("devices.Device", customer=mixer.SELECT)
response = client.get("/update/device/{}/".format(device.pk)) response = client.get("/update/device/{}/".format(device.pk))
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, device.name)
response, device.name
)
def test_device_update_view(create_admin_user): def test_device_update_view(create_admin_user):
@ -187,9 +185,7 @@ def test_device_in_net_update_view(create_admin_user):
"mac_address": "", "mac_address": "",
"ip_status": "1", "ip_status": "1",
} }
response = client.post( response = client.post("/update/device-in-net/{}/".format(device_in_net.pk), data)
"/update/device-in-net/{}/".format(device_in_net.pk), data
)
assert response.status_code == 302 assert response.status_code == 302
device_in_net.refresh_from_db() device_in_net.refresh_from_db()
assert device_in_net.ip == data["ip"] assert device_in_net.ip == data["ip"]

View File

@ -9,9 +9,7 @@ pytestmark = pytest.mark.django_db
def test_warranty_create_form(create_admin_user): def test_warranty_create_form(create_admin_user):
create_admin_user() create_admin_user()
form = forms.WarrantyCreateForm(data={}) form = forms.WarrantyCreateForm(data={})
assert ( assert form.is_valid() is False, "Should be false because no data was given"
form.is_valid() is False
), "Should be false because no data was given"
device = mixer.blend("devices.Device") device = mixer.blend("devices.Device")
@ -33,8 +31,7 @@ def test_warranty_create_form(create_admin_user):
form.is_valid() is False form.is_valid() is False
), "Should be false because valid from is before valid until" ), "Should be false because valid from is before valid until"
assert ( assert (
"Valid from date must be before valid until date" "Valid from date must be before valid until date" == form.errors["__all__"][0]
== form.errors["__all__"][0]
) )
@ -51,6 +48,5 @@ def test_warranty_update_form(create_admin_user):
form = forms.WarrantyUpdateForm(data=data) form = forms.WarrantyUpdateForm(data=data)
assert form.is_valid() is False assert form.is_valid() is False
assert ( assert (
"Valid from date must be before valid until date" "Valid from date must be before valid until date" == form.errors["__all__"][0]
== form.errors["__all__"][0]
) )

View File

@ -39,9 +39,7 @@ def test_warranties_view_plenty_of_time(create_admin_user):
user.save() user.save()
device = mixer.blend("devices.Device", customer=fixture["customer"]) device = mixer.blend("devices.Device", customer=fixture["customer"])
more_than_one_year = datetime.date(datetime.today() + timedelta(400)) more_than_one_year = datetime.date(datetime.today() + timedelta(400))
mixer.blend( mixer.blend("devices.Warranty", device=device, valid_until=more_than_one_year)
"devices.Warranty", device=device, valid_until=more_than_one_year
)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/warranties/") response = client.get("/warranties/")
@ -77,9 +75,7 @@ def test_warranties_view_warranty_one_year_till_expiration(create_admin_user):
user.save() user.save()
device = mixer.blend("devices.Device", customer=fixture["customer"]) device = mixer.blend("devices.Device", customer=fixture["customer"])
not_one_year_more = datetime.date(datetime.today() + timedelta(200)) not_one_year_more = datetime.date(datetime.today() + timedelta(200))
mixer.blend( mixer.blend("devices.Warranty", device=device, valid_until=not_one_year_more)
"devices.Warranty", device=device, valid_until=not_one_year_more
)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/warranties/") response = client.get("/warranties/")

View File

@ -4,9 +4,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path( path("customer/<int:pk>/devices/", views.devices_table_view, name="devices"),
"customer/<int:pk>/devices/", views.devices_table_view, name="devices"
),
path("device/<int:pk>/", views.device_detail_view, name="device"), path("device/<int:pk>/", views.device_detail_view, name="device"),
path( path(
"manufacturer/<int:pk>/", "manufacturer/<int:pk>/",

View File

@ -60,16 +60,12 @@ def device_detail_view(request, pk):
def devices_table_view(request, pk): def devices_table_view(request, pk):
table = DevicesTable(Device.objects.filter(customer=pk)) table = DevicesTable(Device.objects.filter(customer=pk))
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
return render( return render(request, "devices/device_list.html", {"devices": table, "pk": pk})
request, "devices/device_list.html", {"devices": table, "pk": pk}
)
@login_required @login_required
def warranties_view(request): def warranties_view(request):
table = WarrantiesTable( table = WarrantiesTable(utils.objects_for_allowed_customers(Warranty, request.user))
utils.objects_for_allowed_customers(Warranty, request.user)
)
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
return render(request, "devices/warranties_list.html", {"devices": table}) return render(request, "devices/warranties_list.html", {"devices": table})
@ -111,9 +107,7 @@ def device_update_view(request, pk):
""" """
template_name = "devices/device_update.html" template_name = "devices/device_update.html"
request.session["device_to_update"] = pk request.session["device_to_update"] = pk
device = utils.get_object_with_view_permission( device = utils.get_object_with_view_permission(Device, user=request.user, pk=pk)
Device, user=request.user, pk=pk
)
if request.method == "POST" and "save_device" in request.POST: if request.method == "POST" and "save_device" in request.POST:
form = DeviceUpdateForm(request, request.POST, instance=device) form = DeviceUpdateForm(request, request.POST, instance=device)
if form.is_valid(): if form.is_valid():
@ -124,7 +118,7 @@ def device_update_view(request, pk):
return TemplateResponse(request, template_name, {"form": form}) return TemplateResponse(request, template_name, {"form": form})
class DeviceDeleteView(LoginRequiredMixin, DeleteView): class DeviceDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Device model = Device
def get_success_url(self): def get_success_url(self):
@ -160,7 +154,7 @@ class WarrantyUpdateView(LoginRequiredMixin, UpdateView):
return self.request.POST.get("previous_page") return self.request.POST.get("previous_page")
class WarrantyDeleteView(LoginRequiredMixin, DeleteView): class WarrantyDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Warranty model = Warranty
def get_success_url(self): def get_success_url(self):
@ -195,7 +189,7 @@ class DeviceInNetUpdateView(LoginRequiredMixin, UpdateView):
return self.request.POST.get("previous_page") return self.request.POST.get("previous_page")
class DeviceInNetDeleteView(LoginRequiredMixin, DeleteView): class DeviceInNetDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = DeviceInNet model = DeviceInNet
template_name = "devices/device_in_net_confirm_delete.html" template_name = "devices/device_in_net_confirm_delete.html"

View File

@ -48,9 +48,7 @@ class LicenseWithUser(models.Model):
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint( models.UniqueConstraint(fields=["user", "license"], name="user per license")
fields=["user", "license"], name="user per license"
)
] ]

View File

@ -61,9 +61,7 @@ def test_customer_license_table_no_license(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/customer/" + str(customer.id) + "/licenses/") response = client.get("/customer/" + str(customer.id) + "/licenses/")
assert response.status_code == 200 and helper.not_in_content( assert response.status_code == 200 and helper.not_in_content(response, customer)
response, customer
)
def test_customer_license_table_no_permission(create_admin_user): def test_customer_license_table_no_permission(create_admin_user):

View File

@ -41,7 +41,7 @@ def licenses_table_view(request, pk):
class LicenseWithComputerCreateView(LoginRequiredMixin, CreateView): class LicenseWithComputerCreateView(LoginRequiredMixin, CreateView):
model = LicenseWithComputer model = LicenseWithComputer
template_name = "licenses/license_with_computer_create.html" template_name = "licenses/license_with_computer_create.html"
fields = "__all__" fields = "__all__" # type: ignore
def get_success_url(self): def get_success_url(self):
return reverse("computer", args=(self.computer.pk,)) return reverse("computer", args=(self.computer.pk,))
@ -57,7 +57,7 @@ class LicenseWithComputerCreateView(LoginRequiredMixin, CreateView):
} }
class LicenseWithComputerDeleteView(LoginRequiredMixin, DeleteView): class LicenseWithComputerDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = LicenseWithComputer model = LicenseWithComputer
template_name = "licenses/license_with_computer_confirm_delete.html" template_name = "licenses/license_with_computer_confirm_delete.html"
@ -65,7 +65,7 @@ class LicenseWithComputerDeleteView(LoginRequiredMixin, DeleteView):
return reverse("computer", args=(self.object.computer.pk,)) return reverse("computer", args=(self.object.computer.pk,))
class UserLicenseDeleteView(LoginRequiredMixin, DeleteView): class UserLicenseDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = UserLicense model = UserLicense
template_name = "licenses/license_confirm_delete.html" template_name = "licenses/license_confirm_delete.html"
@ -73,7 +73,7 @@ class UserLicenseDeleteView(LoginRequiredMixin, DeleteView):
return reverse("licenses", args=(self.object.customer.pk,)) return reverse("licenses", args=(self.object.customer.pk,))
class ComputerLicenseDeleteView(LoginRequiredMixin, DeleteView): class ComputerLicenseDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = ComputerLicense model = ComputerLicense
template_name = "licenses/license_confirm_delete.html" template_name = "licenses/license_confirm_delete.html"

View File

@ -3,9 +3,7 @@ import os
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
os.environ.setdefault( os.environ.setdefault("DJANGO_SETTINGS_MODULE", "network_inventory.settings")
"DJANGO_SETTINGS_MODULE", "network_inventory.settings"
)
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:

View File

@ -11,8 +11,6 @@ def net_view_permission(old_fuction):
if user.has_perm("customers.view_customer", net.customer): if user.has_perm("customers.view_customer", net.customer):
return old_fuction(request, pk) return old_fuction(request, pk)
else: else:
return HttpResponseForbidden( return HttpResponseForbidden("You're not allowed to access this device.")
"You're not allowed to access this device."
)
return new_function return new_function

View File

@ -15,9 +15,7 @@ def test_net_detail_view_no_permission(create_admin_user):
net = mixer.blend("nets.Net") net = mixer.blend("nets.Net")
customer = mixer.blend("customers.Customer") customer = mixer.blend("customers.Customer")
device = mixer.blend("computers.Computer", customer=customer) device = mixer.blend("computers.Computer", customer=customer)
mixer.blend( mixer.blend("devices.DeviceInNet", device=device, net=net, ip="10.7.89.101")
"devices.DeviceInNet", device=device, net=net, ip="10.7.89.101"
)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/net/" + str(net.id) + "/") response = client.get("/net/" + str(net.id) + "/")
@ -28,9 +26,7 @@ def test_net_detail_view(create_admin_user):
fixture = create_admin_user() fixture = create_admin_user()
net = mixer.blend("nets.Net", customer=mixer.SELECT) net = mixer.blend("nets.Net", customer=mixer.SELECT)
device = mixer.blend("computers.Computer", customer=fixture["customer"]) device = mixer.blend("computers.Computer", customer=fixture["customer"])
device_in_net = DeviceInNet.objects.create( device_in_net = DeviceInNet.objects.create(device=device, net=net, ip="10.7.89.101")
device=device, net=net, ip="10.7.89.101"
)
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/net/" + str(net.id) + "/") response = client.get("/net/" + str(net.id) + "/")

View File

@ -29,12 +29,10 @@ def net_detail_view(request, pk):
net = get_object_or_404(Net, pk=pk) net = get_object_or_404(Net, pk=pk)
table = NetDetailTable(DeviceInNet.objects.filter(net=net)) table = NetDetailTable(DeviceInNet.objects.filter(net=net))
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
return render( return render(request, "nets/net_details.html", {"table": table, "net": net})
request, "nets/net_details.html", {"table": table, "net": net}
)
class NetDeleteView(LoginRequiredMixin, DeleteView): class NetDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = Net model = Net
def get_success_url(self): def get_success_url(self):

View File

@ -1,30 +0,0 @@
from socket import gethostname
from socket import gethostbyname_ex
from .base import *
ALLOWED_HOSTS = [
gethostname(),
] + list(set(gethostbyname_ex(gethostname())[2]))
CSRF_TRUSTED_ORIGINS = [
"http://localhost:8080",
]
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
DEBUG = os.environ.get("DJANGO_DEBUG")
CRISPY_FAIL_SILENTLY = not DEBUG
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "postgres",
"USER": "postgres",
"HOST": "db",
"PORT": 5432,
"PASSWORD": "password",
}
}

View File

@ -2,12 +2,15 @@ from socket import gethostname
from socket import gethostbyname from socket import gethostbyname
from socket import getfqdn from socket import getfqdn
import os
from .base import * from .base import *
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
"localhost", "localhost",
"127.0.0.1", "127.0.0.1",
"0.0.0.0",
getfqdn(), getfqdn(),
gethostname(), gethostname(),
gethostbyname(gethostname()), gethostbyname(gethostname()),
@ -26,10 +29,9 @@ CRISPY_FAIL_SILENTLY = not DEBUG
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.postgresql", "ENGINE": "django.db.backends.postgresql",
"NAME": "postgres", "NAME": "django",
"USER": "postgres", "USER": os.environ.get("USER"),
"HOST": "localhost", "HOST": os.environ.get("PGHOST"),
"PORT": 5432, "PORT": os.environ.get("PGPORT"),
"PASSWORD": "password",
} }
} }

View File

@ -11,8 +11,6 @@ def user_view_permission(old_fuction):
if user.has_perm("customers.view_customer", inventory_user.customer): if user.has_perm("customers.view_customer", inventory_user.customer):
return old_fuction(request, pk) return old_fuction(request, pk)
else: else:
return HttpResponseForbidden( return HttpResponseForbidden("You're not allowed to access this device.")
"You're not allowed to access this device."
)
return new_function return new_function

View File

@ -37,9 +37,7 @@ def test_customer_user_table_no_user(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/customer/" + str(customer.id) + "/users/") response = client.get("/customer/" + str(customer.id) + "/users/")
assert response.status_code == 200 and helper.not_in_content( assert response.status_code == 200 and helper.not_in_content(response, customer)
response, customer
)
def test_customer_user_table_no_permission(create_admin_user): def test_customer_user_table_no_permission(create_admin_user):

View File

@ -61,6 +61,4 @@ def test_group_detail_view_with_child_group(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/group/" + str(group.id) + "/") response = client.get("/group/" + str(group.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, child_group)
response, child_group
)

View File

@ -38,9 +38,7 @@ def test_user_detail_view_group(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/user/" + str(user.id) + "/") response = client.get("/user/" + str(user.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, "Groups")
response, "Groups"
)
def test_user_detail_view_mail_alias(create_admin_user): def test_user_detail_view_mail_alias(create_admin_user):
@ -50,9 +48,7 @@ def test_user_detail_view_mail_alias(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/user/" + str(user.id) + "/") response = client.get("/user/" + str(user.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, "Mail Alias")
response, "Mail Alias"
)
def test_user_detail_view_license(create_admin_user): def test_user_detail_view_license(create_admin_user):
@ -63,9 +59,7 @@ def test_user_detail_view_license(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/user/" + str(user.id) + "/") response = client.get("/user/" + str(user.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, "License")
response, "License"
)
def test_user_detail_view_computer(create_admin_user): def test_user_detail_view_computer(create_admin_user):
@ -75,9 +69,7 @@ def test_user_detail_view_computer(create_admin_user):
client = Client() client = Client()
client.login(username="pharma-admin", password="password") client.login(username="pharma-admin", password="password")
response = client.get("/user/" + str(user.id) + "/") response = client.get("/user/" + str(user.id) + "/")
assert response.status_code == 200 and helper.in_content( assert response.status_code == 200 and helper.in_content(response, computer)
response, computer
)
def test_user_detail_view_no_permission(create_admin_user): def test_user_detail_view_no_permission(create_admin_user):

View File

@ -52,7 +52,7 @@ def user_detail_view(request, pk):
) )
class UserDeleteView(LoginRequiredMixin, DeleteView): class UserDeleteView(LoginRequiredMixin, DeleteView): # type: ignore
model = User model = User
def get_success_url(self): def get_success_url(self):
@ -64,9 +64,7 @@ class UserDeleteView(LoginRequiredMixin, DeleteView):
def groups_table_view(request, pk): def groups_table_view(request, pk):
customer = get_object_or_404(Customer, pk=pk) customer = get_object_or_404(Customer, pk=pk)
groups_table = GroupsTable( groups_table = GroupsTable(
utils.get_objects_for_customer( utils.get_objects_for_customer(Group, user=request.user, customer_pk=pk)
Group, user=request.user, customer_pk=pk
)
) )
RequestConfig(request).configure(groups_table) RequestConfig(request).configure(groups_table)
return TemplateResponse( return TemplateResponse(
@ -81,9 +79,7 @@ def groups_table_view(request, pk):
@login_required @login_required
def group_detail_view(request, pk): def group_detail_view(request, pk):
group = utils.get_object_with_view_permission( group = utils.get_object_with_view_permission(Group, user=request.user, pk=pk)
Group, user=request.user, pk=pk
)
users = group.user_set.all() users = group.user_set.all()
groups = Group.objects.filter(parent_group=group) groups = Group.objects.filter(parent_group=group)
print(groups) print(groups)
@ -96,9 +92,7 @@ def group_detail_view(request, pk):
@login_required @login_required
def delete_group(request, pk): def delete_group(request, pk):
group = utils.get_object_with_view_permission( group = utils.get_object_with_view_permission(Group, user=request.user, pk=pk)
Group, user=request.user, pk=pk
)
if request.method == "POST": if request.method == "POST":
group.delete() group.delete()
return redirect("groups", pk=group.customer.pk) return redirect("groups", pk=group.customer.pk)