├── .flake8 ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation_change.yml │ ├── feature_request.yml │ └── housekeeping.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── dependency-review.yml │ └── python-publish.yml ├── .gitignore ├── .jscpd.json ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── docs └── img │ ├── new_storage_device.png │ ├── storage_device_individual.png │ └── storage_devices.png ├── example_data └── serials.csv ├── netbox_physical_storage ├── __init__.py ├── api │ ├── __init__.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── filtersets.py ├── forms.py ├── graphql.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_remove_storagedevice_mount_point.py │ └── __init__.py ├── models.py ├── navigation.py ├── search.py ├── tables.py ├── templates │ └── netbox_physical_storage │ │ └── storagedevice.html ├── urls.py ├── version.py └── views.py └── setup.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 140 3 | extend-ignore = E203 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | description: Report a reproducible bug in the current release of this NetBox Plugin 4 | title: "[Bug]: " 5 | labels: ["bug"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: > 10 | **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox plugin 11 | installation. 12 | 13 | - Check the release notes: 14 | https://github.com/ryanmerolle/netbox-physical-storage/releases 15 | - Look through the issues already resolved: 16 | https://github.com/ryanmerolle/netbox-physical-storage/issues?q=is%3Aclosed 17 | - Post to Github Discussions if you need setup or usage help that is not a bug: 18 | https://github.com/ryanmerolle/netbox-physical-storage/discussions 19 | - Join the `#netbox` channel on our Slack: 20 | https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ 21 | 22 | - type: input 23 | attributes: 24 | label: NetBox access-list plugin version 25 | description: What version of the NetBox access-list plugin are you currently running? 26 | placeholder: v1.3.0 27 | validations: 28 | required: true 29 | - type: input 30 | attributes: 31 | label: NetBox version 32 | description: What version of NetBox are you currently running? 33 | placeholder: v3.5.4 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: Steps to Reproduce 39 | description: > 40 | Describe in detail the exact steps that someone else can take to 41 | reproduce this bug using the current stable release of the NetBox access-list plugin. 42 | #placeholder: | 43 | validations: 44 | required: true 45 | - type: textarea 46 | attributes: 47 | label: Expected Behavior 48 | description: What did you expect to happen? 49 | placeholder: A new widget should have been created with the specified attributes 50 | validations: 51 | required: true 52 | - type: textarea 53 | attributes: 54 | label: Observed Behavior 55 | description: What happened instead? 56 | placeholder: A TypeError exception was raised 57 | validations: 58 | required: true 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 3 | blank_issues_enabled: false 4 | contact_links: 5 | #- name: 📕 Plugin Documentation 6 | # url: https://netbox-physical-storage.readthedocs.io 7 | # about: "Please refer to the documentation before raising a bug or feature request." 8 | - name: 📖 Contributing Policy 9 | url: https://github.com/Zorlin/netbox-physical-storage/blob/dev/CONTRIBUTING.md 10 | about: "Please read through our contributing policy before opening an issue or pull request" 11 | - name: 💬 Community Slack 12 | url: https://netdev.chat/ 13 | about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems" 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_change.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📖 Documentation Change 3 | description: Suggest an addition or modification to the NetBox Access Lists plugin documentation 4 | title: "[Docs]: " 5 | labels: ["documentation"] 6 | body: 7 | - type: dropdown 8 | attributes: 9 | label: Change Type 10 | description: What type of change are you proposing? 11 | options: 12 | - Addition 13 | - Correction 14 | - Removal 15 | - Cleanup (formatting, typos, etc.) 16 | validations: 17 | required: true 18 | - type: dropdown 19 | attributes: 20 | label: Area 21 | description: To what section of the documentation does this change primarily pertain? 22 | options: 23 | - Installation instructions 24 | - Configuration parameters 25 | - Functionality/features 26 | - Administration/development 27 | - Other 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Proposed Changes 33 | description: Describe the proposed changes and why they are necessary. 34 | validations: 35 | required: true 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | description: Propose a new feature or enhancement 4 | title: "[Feature]: " 5 | labels: ["enhancement"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: > 10 | **NOTE:** This form is only for submitting well-formed proposals to extend or modify 11 | NetBox in some way. If you're trying to solve a problem but can't figure out how, or if 12 | you still need time to work on the details of a proposed new feature, please start a 13 | [discussion](https://github.com/Zorlin/netbox-physical-storage/discussions) instead. 14 | - type: input 15 | attributes: 16 | label: NetBox version 17 | description: What version of NetBox are you currently running? 18 | placeholder: v3.5.4 19 | validations: 20 | required: true 21 | - type: dropdown 22 | attributes: 23 | label: Feature type 24 | options: 25 | - New Model to plugin 26 | - Change to existing model 27 | - Add a function 28 | - Remove a function 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: Proposed functionality 34 | description: > 35 | Describe in detail the new feature or behavior you are proposing. Include any specific changes 36 | to work flows, data models, and/or dependencies. The more detail you provide here, the 37 | greater chance your proposal has of being discussed. Feature requests which don't include an 38 | actionable implementation plan will be rejected. 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: Use case 44 | description: > 45 | Explain how adding this functionality would benefit NetBox users & specifically this plugin. What need does it address? 46 | validations: 47 | required: true 48 | - type: textarea 49 | attributes: 50 | label: External dependencies 51 | description: > 52 | List any new dependencies on external libraries or services that this new feature would 53 | introduce. For example, does the proposal require the installation of a new Python package? 54 | (Not all new features introduce new dependencies.) 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/housekeeping.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🏡 Housekeeping 3 | description: A change pertaining to the codebase itself (developers only) 4 | title: "[Housekeeping]: " 5 | labels: ["housekeeping"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: > 10 | **NOTE:** This template is for use by maintainers only. Please do not submit 11 | an issue using this template unless you have been specifically asked to do so. 12 | - type: textarea 13 | attributes: 14 | label: Proposed Changes 15 | description: > 16 | Describe in detail the new feature or behavior you'd like to propose. 17 | Include any specific changes to work flows, data models, or the user interface. 18 | validations: 19 | required: true 20 | - type: textarea 21 | attributes: 22 | label: Justification 23 | description: Please provide justification for the proposed change(s). 24 | validations: 25 | required: true 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # To get started with Dependabot version updates, you'll need to specify which 3 | # package ecosystems to update and where the package manifests are located. 4 | # Please see the documentation for all configuration options: 5 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "pip" # See documentation for possible values 10 | directory: "/" # Location of package manifests 11 | schedule: 12 | interval: "weekly" 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 14 | # Pull Request 15 | 16 | ## Related Issue 17 | 18 | 21 | 22 | ## New Behavior 23 | 24 | 27 | 28 | ... 29 | 30 | ## Contrast to Current Behavior 31 | 32 | 36 | 37 | ... 38 | 39 | ## Discussion: Benefits and Drawbacks 40 | 41 | 53 | 54 | ... 55 | 56 | ## Changes to the Documentation 57 | 58 | 62 | 63 | ... 64 | 65 | ## Proposed Release Note Entry 66 | 67 | 71 | 72 | ... 73 | 74 | ## Double Check 75 | 76 | 79 | 80 | * [ ] I have explained my PR according to the information in the comments 81 | or in a linked issue. 82 | * [ ] My PR targets the `dev` branch. 83 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | pull_request: 7 | 8 | # This ensures that previous jobs for the workflow are canceled when the ref is 9 | # updated. 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | run-lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | # Full git history is needed to get a proper list of changed files within `super-linter` 22 | fetch-depth: 0 23 | 24 | - name: Lint Code Base 25 | uses: github/super-linter/slim@v5 26 | env: 27 | DEFAULT_BRANCH: dev 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SUPPRESS_POSSUM: true 30 | LINTER_RULES_PATH: / 31 | VALIDATE_ALL_CODEBASE: false 32 | VALIDATE_DOCKERFILE: false 33 | VALIDATE_JSCPD: true 34 | FILTER_REGEX_EXCLUDE: (.*/)?(configuration/.*) 35 | 36 | test: 37 | runs-on: ubuntu-latest 38 | name: Runs plugin tests 39 | needs: run-lint 40 | steps: 41 | - id: git-checkout 42 | name: Checkout 43 | uses: actions/checkout@v4 44 | 45 | - id: docker-test 46 | name: Test the image 47 | run: ./test.sh snapshot 48 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # For most projects, this workflow file will not need changing; you simply need 3 | # to commit it to your repository. 4 | # 5 | # You may wish to alter this file to override the set of languages analyzed, 6 | # or to provide custom queries or build logic. 7 | # 8 | # ******** NOTE ******** 9 | # We have attempted to detect the languages in your repository. Please check 10 | # the `language` matrix defined below to confirm you have the correct set of 11 | # supported CodeQL languages. 12 | # 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: ["dev"] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: ["dev"] 21 | schedule: 22 | - cron: '24 4 * * 5' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: ['python'] 37 | # CodeQL supports ['cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby'] 38 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | 53 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 54 | # queries: security-extended,security-and-quality 55 | 56 | 57 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 58 | # If this step fails, then you should remove it and run the build manually (see below) 59 | - name: Autobuild 60 | uses: github/codeql-action/autobuild@v3 61 | 62 | # ℹ️ Command-line programs to run using the OS shell. 63 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 64 | 65 | # If the Autobuild fails above, remove it and uncomment the following three lines. 66 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 67 | 68 | # - run: | 69 | # echo "Run, Build Application using script" 70 | # ./location_of_script_within_repo/buildscript.sh 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v3 74 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Dependency Review Action 3 | # 4 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 5 | # 6 | # Source repository: https://github.com/actions/dependency-review-action 7 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 8 | name: 'Dependency Review' 9 | on: [pull_request] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | dependency-review: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: 'Checkout Repository' 19 | uses: actions/checkout@v4 20 | - name: 'Dependency Review' 21 | uses: actions/dependency-review-action@v3 22 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This workflow will upload a Python Package using Twine when a release is created 3 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 4 | 5 | # This workflow uses actions that are not certified by GitHub. 6 | # They are provided by a third-party and are governed by 7 | # separate terms of service, privacy policy, and support 8 | # documentation. 9 | 10 | name: Upload Python Package 11 | 12 | on: 13 | release: 14 | types: [published] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | deploy: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install build 34 | - name: Build package 35 | run: python -m build 36 | - name: Publish package 37 | uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf 38 | with: 39 | user: __token__ 40 | password: ${{ secrets.PYPI_API_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | #.env 124 | .venv 125 | #env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # VS Code 163 | .vscode/ 164 | # JetBrains 165 | .idea/ 166 | 167 | # Temporary files 168 | *.tmp 169 | tmp/ 170 | 171 | # coverage 172 | coverage/ 173 | htmlcov/ 174 | .coverage 175 | .coverage.* 176 | coverage.xml 177 | *.cover 178 | 179 | # ruff 180 | .ruff_cache/ 181 | -------------------------------------------------------------------------------- /.jscpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "threshold": 10, 3 | "ignore": ["**/migrations/**", "**/tests/**"] 4 | } 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.4.0 5 | hooks: 6 | - id: check-docstring-first 7 | - id: check-merge-conflict 8 | - id: check-yaml 9 | - id: debug-statements 10 | - id: end-of-file-fixer 11 | - id: name-tests-test 12 | args: 13 | - "--django" 14 | - id: requirements-txt-fixer 15 | - id: trailing-whitespace 16 | - repo: https://github.com/PyCQA/isort 17 | rev: 5.12.0 18 | hooks: 19 | - id: isort 20 | args: 21 | - "--profile=black" 22 | exclude: ^.devcontainer/ 23 | - repo: https://github.com/psf/black 24 | rev: 23.3.0 25 | hooks: 26 | - id: black 27 | language_version: python3 28 | exclude: ^.devcontainer/ 29 | - repo: https://github.com/asottile/add-trailing-comma 30 | rev: v2.5.1 31 | hooks: 32 | - id: add-trailing-comma 33 | args: 34 | - "--py36-plus" 35 | - repo: https://github.com/PyCQA/flake8 36 | rev: 6.0.0 37 | hooks: 38 | - id: flake8 39 | exclude: ^.devcontainer/ 40 | - repo: https://github.com/asottile/pyupgrade 41 | rev: v3.7.0 42 | hooks: 43 | - id: pyupgrade 44 | args: 45 | - "--py39-plus" 46 | - repo: https://github.com/adrienverge/yamllint 47 | rev: v1.32.0 48 | hooks: 49 | - id: yamllint 50 | - repo: https://github.com/econchick/interrogate 51 | rev: 1.5.0 52 | hooks: 53 | - id: interrogate 54 | args: [--fail-under=90, --verbose] 55 | exclude: (^.devcontainer/|^netbox_physical_storage/migrations/) 56 | #- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs 57 | # rev: v1.1.2 58 | # hooks: 59 | # - id: htmlhint 60 | # args: [--config, .htmlhintrc] 61 | - repo: https://github.com/igorshubovych/markdownlint-cli 62 | rev: v0.35.0 63 | hooks: 64 | - id: markdownlint 65 | - repo: https://github.com/astral-sh/ruff-pre-commit 66 | rev: v0.0.272 67 | hooks: 68 | - id: ruff 69 | #- repo: local 70 | # hooks: 71 | # - id: wily 72 | # name: wily 73 | # entry: wily diff 74 | # verbose: true 75 | # language: python 76 | # additional_dependencies: [wily] 77 | #- repo: https://github.com/sourcery-ai/sourcery 78 | # rev: v1.0.4b23 79 | # hooks: 80 | # - id: sourcery 81 | # # The best way to use Sourcery in a pre-commit hook: 82 | # # * review only changed lines: 83 | # # * omit the summary 84 | # args: 85 | # - --diff=git diff HEAD 86 | # - --no-summary 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | These are notes from the early development versions. 2 | 3 | v0.0.2 - Backwards Forwards 4 | * [BREAKING] Removes the "Mount point" option for now 5 | * [bugfix] Fix display of serial numbers in the main StorageDevice view 6 | 7 | v0.0.1 - The First Drop 8 | * Basic functionality 9 | * Can set the following: 10 | * Name 11 | * Type of storage device (SAS, SATA, NVMe) 12 | * Serial number 13 | * Mount point 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NETBOX_VARIANT=v3.6.8 2 | 3 | FROM netboxcommunity/netbox:${NETBOX_VARIANT} 4 | 5 | RUN mkdir -pv /plugins/netbox-physical-storage 6 | COPY . /plugins/netbox-physical-storage 7 | 8 | RUN /opt/netbox/venv/bin/python3 /plugins/netbox-physical-storage/setup.py develop && \ 9 | cp -rf /plugins/netbox-physical-storage/netbox_physical_storage/ /opt/netbox/venv/lib/python3.11/site-packages/netbox_physical_storage 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Benjamin Arntzen. 4 | Portions taken from other projects, see Credits section for more information. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetBox Physical Storage Plugin 2 | 3 | A [Netbox](https://github.com/netbox-community/netbox) plugin for management of physical storage devices such as hard drives, solid state disks and NVMes, and more. 4 | 5 | ***This is probably not that useful to you yet, and is changing rapidly! HERE BE DRAGONS, BE WARNED.*** 6 | 7 | ## Features 8 | 9 | This plugin currently allows you to do the following: 10 | 11 | - Add, track and search storage devices by name + serial number + device type 12 | 13 | ## Future features 14 | 15 | These are things I've been thinking about, but haven't implemented. 16 | 17 | - Add and manage manufacturers + device models 18 | - DPWD information 19 | - Drive size 20 | - Drive class (enterprise, consumer, nearline, read-intensive, write-intensive) 21 | - Spindle technology (HMR, SMR, CMR) 22 | - History tracking 23 | - Storage devices can live for a long time. It would be nice to be able to track changes to them over time. 24 | Where was this drive installed? In which server? Which slot? When was it replaced? What was it replaced with? 25 | Is it deprecated? Is it active? Is it dead? Was it RMA'd and replaced with a new drive? 26 | - Extend the Devices model to allow you to add "Storage Bays" as as a component 27 | - Enable you to then assign Storage Devices to those Storage Bays 28 | - SAS cabling/topology (potentially with visualisation as a stretch goal?) 29 | - ZFS, LVM and RAID modeling/tracking 30 | 31 | ## Credits 32 | 33 | Based on the NetBox plugin tutorial by [jeremystretch](https://github.com/jeremystretch): 34 | 35 | - [demo repository](https://github.com/netbox-community/netbox-plugin-demo) 36 | - [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial) 37 | 38 | Portions taken from [Ryan Merolle's netbox-acls](https://github.com/ryanmerolle/netbox-acls) 39 | Portions taken from [tbotnz/netbox_floorplan](https://github.com/tbotnz/netbox_floorplan/tree/master) 40 | 41 | Thank you to the above authors for the great examples! 42 | 43 | ## Contributing 44 | 45 | This project is currently maintained by [Benjamin Arntzen](https://github.com/zorlin) 46 | 47 | See the [CONTRIBUTING](CONTRIBUTING.md) for more information. 48 | 49 | ## Compatibility 50 | 51 | Each Plugin version listed below has been tested with its corresponding NetBox version. 52 | 53 | | NetBox version | Plugin version | 54 | |:--------------:|:--------------:| 55 | | 3.6 | 0.0.2 | 56 | | 3.6 | 0.0.1 | 57 | 58 | ## Installing 59 | 60 | For adding to a NetBox Docker setup see 61 | [the general instructions for using netbox-docker with plugins](https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins). 62 | 63 | You can install with pip: 64 | 65 | ```bash 66 | pip install netbox-physical-storage 67 | ``` 68 | 69 | or by adding to your `local_requirements.txt` or `plugin_requirements.txt` (netbox-docker): 70 | 71 | ```bash 72 | netbox-physical-storage 73 | ``` 74 | 75 | ## Configuration 76 | 77 | Enable the plugin in `/opt/netbox/netbox/netbox/configuration.py`, 78 | or if you use netbox-docker, your `/configuration/plugins.py` file : 79 | 80 | ```python 81 | PLUGINS = [ 82 | "netbox_physical-storage" 83 | ] 84 | 85 | PLUGINS_CONFIG = { 86 | "netbox_physical-storage": { 87 | "top_level_menu": True # If set to True the plugin will add a top level menu item for the plugin. If set to False the plugin will add a menu item under the Plugins menu item. Default is set to True. 88 | }, 89 | } 90 | ``` 91 | 92 | ## Screenshots 93 | 94 | Storage Devices - List View 95 | ![Storage Devices - List View](docs/img/storage_devices.png) 96 | 97 | Storage Devices - Individual View 98 | ![Storage Devices - Individual View](docs/img/storage_device_individual.png) 99 | 100 | Storage Devices - Adding a Device 101 | ![Storage Devices - Adding a Device](docs/img/new_storage_device.png) 102 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Feel free to raise an issue about any vunerabilities introduced in this plugin. 6 | -------------------------------------------------------------------------------- /docs/img/new_storage_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zorlin/netbox-physical-storage/aac8fa7108bf1e0e12106c8c5162c46c91c0cffa/docs/img/new_storage_device.png -------------------------------------------------------------------------------- /docs/img/storage_device_individual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zorlin/netbox-physical-storage/aac8fa7108bf1e0e12106c8c5162c46c91c0cffa/docs/img/storage_device_individual.png -------------------------------------------------------------------------------- /docs/img/storage_devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zorlin/netbox-physical-storage/aac8fa7108bf1e0e12106c8c5162c46c91c0cffa/docs/img/storage_devices.png -------------------------------------------------------------------------------- /example_data/serials.csv: -------------------------------------------------------------------------------- 1 | Name,Storage device type,Serial number 2 | sda,SATA,YJUVWOLX9NVBDBT 3 | sdb,SATA,DVZN508A04J9FY3 4 | sdc,SATA,YKVL6QF3G0KS475 5 | sdd,SATA,AM03LTQZJJCXEU7 6 | sde,SATA,WEZF9U6L6Z0EBES 7 | sdf,SATA,857E8NR8576M 8 | sdg,SATA,EQT6FUEWPFWK 9 | sdh,SATA,HJSAPO0Z7HQ4 10 | sdi,SATA,ET5QMRQTRHDX 11 | sdj,SATA,JP1ICH9UE9XR 12 | sdk,SATA,938HUI8O0MTZ 13 | sdl,SATA,ZIX8620FN0VS 14 | sdm,SATA,JZ1VF24H1M5J 15 | sdn,SATA,4XFK605F3OMH 16 | sdo,SATA,L8AD04F7818K 17 | sdp,SATA,SKRX0B9YLDJ6 18 | sdq,SATA,UTHL9IE30NT5 19 | sdr,SATA,WKYJURN90S87 20 | sds,SATA,QO6EPIE1AIGL 21 | sdt,SATA,KQ01YBPGWR1E 22 | sdu,SATA,LKXLG49AV4KA 23 | sdv,SATA,HJ0AWJQ0OB06 24 | sdw,SATA,1NCYZM1X5BS1 25 | sdx,SATA,PSVROJ7H0NIL 26 | -------------------------------------------------------------------------------- /netbox_physical_storage/__init__.py: -------------------------------------------------------------------------------- 1 | from extras.plugins import PluginConfig 2 | 3 | class NetBoxPhysicalStorageConfig(PluginConfig): 4 | name = 'netbox_physical_storage' 5 | verbose_name = ' NetBox Physical Storage' 6 | description = 'Manage physical storage interfaces and devices in NetBox.' 7 | version = '0.0.1' 8 | base_url = 'physical-storage' 9 | min_version = '3.4.0' 10 | 11 | config = NetBoxPhysicalStorageConfig -------------------------------------------------------------------------------- /netbox_physical_storage/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zorlin/netbox-physical-storage/aac8fa7108bf1e0e12106c8c5162c46c91c0cffa/netbox_physical_storage/api/__init__.py -------------------------------------------------------------------------------- /netbox_physical_storage/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer 3 | from ..models import StorageDevice 4 | 5 | class StorageDeviceSerializer(NetBoxModelSerializer): 6 | url = serializers.HyperlinkedIdentityField( 7 | view_name='plugins-api:netbox_physical_storage-api:storagedevice-detail' 8 | ) 9 | class Meta: 10 | model = StorageDevice 11 | fields = [ 12 | 'id', 'url', 'name', 'storage_device_type', 'serial_number', 'comments', 'tags', 'custom_fields', 'created', 13 | 'last_updated', 14 | ] 15 | 16 | # TODO: This entire class may be unnecessary, as I don't know that we need nested serializers for this plugin. 17 | # 2023-12-31: Bye 2023! See you in a year... 18 | class NestedStorageDeviceSerializer(WritableNestedSerializer): 19 | url = serializers.HyperlinkedIdentityField( 20 | view_name='plugins-api:netbox_physical_storage-api:storagedevice-detail' 21 | ) 22 | 23 | class Meta: 24 | model = StorageDevice 25 | fields = ('id', 'url', 'display', 'name') 26 | -------------------------------------------------------------------------------- /netbox_physical_storage/api/urls.py: -------------------------------------------------------------------------------- 1 | from netbox.api.routers import NetBoxRouter 2 | from . import views 3 | 4 | app_name = 'netbox_physical_storage' 5 | 6 | router = NetBoxRouter() 7 | router.register('storage-devices', views.StorageDeviceViewSet) 8 | 9 | urlpatterns = router.urls -------------------------------------------------------------------------------- /netbox_physical_storage/api/views.py: -------------------------------------------------------------------------------- 1 | from netbox.api.viewsets import NetBoxModelViewSet 2 | 3 | from .. import filtersets, models 4 | from .serializers import StorageDeviceSerializer 5 | 6 | class StorageDeviceViewSet(NetBoxModelViewSet): 7 | queryset = models.StorageDevice.objects.prefetch_related('tags') 8 | serializer_class = StorageDeviceSerializer 9 | -------------------------------------------------------------------------------- /netbox_physical_storage/filtersets.py: -------------------------------------------------------------------------------- 1 | from netbox.filtersets import NetBoxModelFilterSet 2 | from .models import StorageDevice 3 | 4 | class StorageDeviceFilterSet(NetBoxModelFilterSet): 5 | 6 | class Meta: 7 | model = StorageDevice 8 | fields = ('name', 'storage_device_type', 'serial_number', 'comments') 9 | 10 | def search(self, queryset, name, value): 11 | return queryset.filter(description__icontains=value) 12 | -------------------------------------------------------------------------------- /netbox_physical_storage/forms.py: -------------------------------------------------------------------------------- 1 | from .models import StorageDevice, StorageDeviceTypeChoices 2 | from utilities.forms.fields import CommentField 3 | from django import forms 4 | from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm 5 | 6 | class StorageDeviceFilterForm(NetBoxModelFilterSetForm): 7 | model = StorageDevice 8 | 9 | # TODO We're using Django's ModelMultipleChoiceField class for this field instead of NetBox's DynamicModelChoiceField because the latter requires a functional REST API endpoint for the model. Once we implement a REST API in step nine, you're free to revisit this form and change access_list to a DynamicModelChoiceField. 10 | storage_device = forms.ModelMultipleChoiceField( 11 | queryset=StorageDevice.objects.all(), 12 | required=False 13 | ) 14 | 15 | storage_device_type = forms.MultipleChoiceField( 16 | choices=StorageDeviceTypeChoices, 17 | required=False 18 | ) 19 | 20 | class StorageDeviceForm(NetBoxModelForm): 21 | comments = CommentField() 22 | 23 | class Meta: 24 | model = StorageDevice 25 | fields = ('name', 'storage_device_type', 'serial_number', 'comments') 26 | -------------------------------------------------------------------------------- /netbox_physical_storage/graphql.py: -------------------------------------------------------------------------------- 1 | from graphene import ObjectType 2 | from netbox.graphql.types import NetBoxObjectType 3 | from netbox.graphql.fields import ObjectField, ObjectListField 4 | from . import filtersets, models 5 | 6 | class StorageDeviceType(NetBoxObjectType): 7 | 8 | class Meta: 9 | model = models.StorageDevice 10 | fields = '__all__' 11 | 12 | class Query(ObjectType): 13 | storage_device = ObjectField(StorageDeviceType) 14 | storage_device_list = ObjectListField(StorageDeviceType) 15 | 16 | schema = Query 17 | -------------------------------------------------------------------------------- /netbox_physical_storage/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.8 on 2023-12-31 01:39 2 | 3 | from django.db import migrations, models 4 | import taggit.managers 5 | import utilities.json 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('extras', '0105_customfield_min_max_values'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='StorageDevice', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 21 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 22 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 23 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 24 | ('name', models.CharField(max_length=100)), 25 | ('storage_device_type', models.CharField(max_length=100)), 26 | ('serial_number', models.CharField(blank=True, max_length=100)), 27 | ('mount_point', models.CharField(blank=True, max_length=100)), 28 | ('comments', models.TextField(blank=True)), 29 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 30 | ], 31 | options={ 32 | 'ordering': ('name',), 33 | }, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /netbox_physical_storage/migrations/0002_remove_storagedevice_mount_point.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.8 on 2023-12-31 04:42 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_physical_storage', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='storagedevice', 15 | name='mount_point', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /netbox_physical_storage/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zorlin/netbox-physical-storage/aac8fa7108bf1e0e12106c8c5162c46c91c0cffa/netbox_physical_storage/migrations/__init__.py -------------------------------------------------------------------------------- /netbox_physical_storage/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.postgres.fields import ArrayField 2 | from django.db import models 3 | from netbox.models import NetBoxModel 4 | from utilities.choices import ChoiceSet 5 | from django.urls import reverse 6 | 7 | class StorageDeviceTypeChoices(ChoiceSet): 8 | key = 'StorageDevice.type' 9 | 10 | CHOICES = [ 11 | ('SAS', 'SAS', 'blue'), 12 | ('SATA', 'SATA', 'indigo'), 13 | ('NVMe', 'NVMe', 'purple'), 14 | ] 15 | 16 | class StorageDevice(NetBoxModel): 17 | name = models.CharField( 18 | max_length=100 19 | ) 20 | storage_device_type = models.CharField( 21 | max_length=100, 22 | choices=StorageDeviceTypeChoices 23 | ) 24 | serial_number = models.CharField( 25 | max_length=100, 26 | blank=True 27 | ) 28 | comments = models.TextField( 29 | blank=True 30 | ) 31 | 32 | class Meta: 33 | ordering = ('name',) 34 | 35 | def __str__(self): 36 | return self.name 37 | 38 | def get_absolute_url(self): 39 | return reverse('plugins:netbox_physical_storage:storagedevice', args=[self.pk]) 40 | 41 | def get_storage_device_type_color(self): 42 | return StorageDeviceTypeChoices.colors.get(self.storage_device_type) 43 | -------------------------------------------------------------------------------- /netbox_physical_storage/navigation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Define the plugin menu buttons & the plugin navigation bar enteries. 3 | """ 4 | 5 | from django.conf import settings 6 | from extras.plugins import PluginMenu, PluginMenuButton, PluginMenuItem 7 | from utilities.choices import ButtonColorChoices 8 | 9 | plugin_settings = settings.PLUGINS_CONFIG["netbox_physical_storage"] 10 | 11 | # 12 | # Define storage device menu buttons 13 | # 14 | storagedevice_buttons = [ 15 | PluginMenuButton( 16 | link='plugins:netbox_physical_storage:storagedevice_add', 17 | title='Add', 18 | icon_class='mdi mdi-plus-thick', 19 | color=ButtonColorChoices.GREEN, 20 | ) 21 | ] 22 | 23 | # 24 | # Define the top-level menu 25 | # 26 | menu_buttons = ( 27 | PluginMenuItem( 28 | link='plugins:netbox_physical_storage:storagedevice_list', 29 | link_text='Storage Devices', 30 | buttons=storagedevice_buttons, 31 | ), 32 | ) 33 | 34 | if plugin_settings.get("top_level_menu"): 35 | menu = PluginMenu( 36 | label="Physical Storage", 37 | groups=(("Storage Devices", menu_buttons),), 38 | icon_class="mdi mdi-harddisk", 39 | ) 40 | else: 41 | menu_items = menu_buttons -------------------------------------------------------------------------------- /netbox_physical_storage/search.py: -------------------------------------------------------------------------------- 1 | from netbox.search import SearchIndex, register_search 2 | from .models import StorageDevice 3 | 4 | @register_search 5 | 6 | class StorageDeviceIndex(SearchIndex): 7 | model = StorageDevice 8 | fields = ( 9 | ('name', 100), 10 | ('storage_device_type', 50), 11 | ('serial_number', 50), 12 | ('comments', 50), 13 | ) 14 | -------------------------------------------------------------------------------- /netbox_physical_storage/tables.py: -------------------------------------------------------------------------------- 1 | import django_tables2 as tables 2 | 3 | from netbox.tables import NetBoxTable, ChoiceFieldColumn 4 | from .models import StorageDevice 5 | 6 | class StorageDeviceTable(NetBoxTable): 7 | name = tables.Column( 8 | linkify=True 9 | ) 10 | storage_device_type = ChoiceFieldColumn() 11 | 12 | class Meta(NetBoxTable.Meta): 13 | model = StorageDevice 14 | fields = ('pk', 'id', 'name', 'storage_device_type', 'serial_number', 'comments') 15 | default_columns = ('name', 'storage_device_type', 'serial_number') 16 | -------------------------------------------------------------------------------- /netbox_physical_storage/templates/netbox_physical_storage/storagedevice.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %}{% block content %} 2 |
3 |
4 |
5 |
Storage Device
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Name{{ object.name }}
Storage Type{{ object.storage_device_type }}
Serial Number{{ object.serial_number }}
21 |
22 |
23 | {% include 'inc/panels/custom_fields.html' %} 24 |
25 |
26 | {% include 'inc/panels/tags.html' %} 27 | {% include 'inc/panels/comments.html' %} 28 |
29 |
30 | {% endblock content %} 31 | -------------------------------------------------------------------------------- /netbox_physical_storage/urls.py: -------------------------------------------------------------------------------- 1 | from netbox.views.generic import ObjectChangeLogView 2 | from django.urls import path 3 | from . import models, views 4 | 5 | urlpatterns = ( 6 | path('physical-storage/', views.StorageDeviceListView.as_view(), name='storagedevice_list'), 7 | path('physical-storage/add/', views.StorageDeviceEditView.as_view(), name='storagedevice_add'), 8 | path('physical-storage//', views.StorageDeviceView.as_view(), name='storagedevice'), 9 | path('physical-storage//edit/', views.StorageDeviceEditView.as_view(), name='storagedevice_edit'), 10 | path('physical-storage//delete/', views.StorageDeviceDeleteView.as_view(), name='storagedevice_delete'), 11 | path('physical-storage//changelog/', ObjectChangeLogView.as_view(), name='storagedevice_changelog', kwargs={ 12 | 'model': models.StorageDevice 13 | }) 14 | ) -------------------------------------------------------------------------------- /netbox_physical_storage/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | -------------------------------------------------------------------------------- /netbox_physical_storage/views.py: -------------------------------------------------------------------------------- 1 | from netbox.views import generic 2 | from . import filtersets, forms, models, tables 3 | 4 | class StorageDeviceView(generic.ObjectView): 5 | queryset = models.StorageDevice.objects.all() 6 | 7 | class StorageDeviceListView(generic.ObjectListView): 8 | queryset = models.StorageDevice.objects.all() 9 | table = tables.StorageDeviceTable 10 | 11 | class StorageDeviceEditView(generic.ObjectEditView): 12 | queryset = models.StorageDevice.objects.all() 13 | form = forms.StorageDeviceForm 14 | 15 | class StorageDeviceDeleteView(generic.ObjectDeleteView): 16 | queryset = models.StorageDevice.objects.all() 17 | 18 | class StorageDeviceListView(generic.ObjectListView): 19 | queryset = models.StorageDevice.objects.all() 20 | table = tables.StorageDeviceTable 21 | filterset = filtersets.StorageDeviceFilterSet 22 | filterset_form = forms.StorageDeviceFilterForm -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name='netbox-physical-storage', 5 | version='0.1', 6 | description='A NetBox plugin', 7 | install_requires=[], 8 | packages=find_packages(), 9 | include_package_data=True, 10 | zip_safe=False, 11 | ) 12 | --------------------------------------------------------------------------------