From 5f92ed492b73a9fc49c7f34672c28577ec5c1965 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 18 Feb 2022 15:35:08 -0500 Subject: [PATCH] Update development docs --- docs/development/getting-started.md | 53 ++++++++++++------ docs/media/development/github_fork_button.png | Bin 0 -> 1954 bytes docs/media/development/github_fork_dialog.png | Bin 0 -> 19661 bytes 3 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 docs/media/development/github_fork_button.png create mode 100644 docs/media/development/github_fork_dialog.png diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index bc78bf635..dbbe8378d 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -11,17 +11,25 @@ Getting started with NetBox development is pretty straightforward, and should fe ### Fork the Repo -Assuming you'll be working on your own fork, your first step will be to fork the [official git repository](https://github.com/netbox-community/netbox). (If you're a maintainer who's going to be working directly with the official repo, skip this step.) You can then clone your GitHub fork locally for development: +Assuming you'll be working on your own fork, your first step will be to fork the [official git repository](https://github.com/netbox-community/netbox). (If you're a maintainer who's going to be working directly with the official repo, skip this step.) Click the "fork" button at top right (be sure that you've logged into GitHub first). + +![GitHub fork button](../media/development/github_fork_button.png) + +Copy the URL provided in the dialog box. + +![GitHub fork dialog](../media/development/github_fork_dialog.png) + +You can then clone your GitHub fork locally for development: ```no-highlight -$ git clone https://github.com/youruseraccount/netbox.git +$ git clone https://github.com/$username/netbox.git Cloning into 'netbox'... -remote: Enumerating objects: 231, done. -remote: Counting objects: 100% (231/231), done. -remote: Compressing objects: 100% (147/147), done. -remote: Total 56705 (delta 134), reused 145 (delta 84), pack-reused 56474 -Receiving objects: 100% (56705/56705), 27.96 MiB | 34.92 MiB/s, done. -Resolving deltas: 100% (44177/44177), done. +remote: Enumerating objects: 85949, done. +remote: Counting objects: 100% (4672/4672), done. +remote: Compressing objects: 100% (1224/1224), done. +remote: Total 85949 (delta 3538), reused 4332 (delta 3438), pack-reused 81277 +Receiving objects: 100% (85949/85949), 55.16 MiB | 44.90 MiB/s, done. +Resolving deltas: 100% (68008/68008), done. $ ls netbox/ base_requirements.txt contrib docs mkdocs.yml NOTICE requirements.txt upgrade.sh CHANGELOG.md CONTRIBUTING.md LICENSE.txt netbox README.md scripts @@ -33,7 +41,7 @@ The NetBox project utilizes three persistent git branches to track work: * `develop` - All development on the upcoming stable release occurs here * `feature` - Tracks work on an upcoming major release -Typically, you'll base pull requests off of the `develop` branch, or off of `feature` if you're working on a new major release. **Never** merge pull requests into the `master` branch, which receives merged only from the `develop` branch. +Typically, you'll base pull requests off of the `develop` branch, or off of `feature` if you're working on a new major release. **Never** merge pull requests into the `master` branch: This branch only ever merges pull requests from the `develop` branch, to effect a new release. For example, assume that the current NetBox release is v3.1.1. Work applied to the `develop` branch will appear in v3.1.2, and work done under the `feature` branch will be included in the next minor release (v3.2.0). @@ -60,7 +68,7 @@ $ python3 -m venv ~/.venv/netbox This will create a directory named `.venv/netbox/` in your home directory, which houses a virtual copy of the Python executable and its related libraries and tooling. When running NetBox for development, it will be run using the Python binary at `~/.venv/netbox/bin/python`. !!! info "Where to Create Your Virtual Environments" - Keeping virtual environments in `~/.venv/` is a common convention but entirely optional: Virtual environments can be created almost wherever you please. + Keeping virtual environments in `~/.venv/` is a common convention but entirely optional: Virtual environments can be created almost wherever you please. Also consider using [`virtualenvwrapper`](https://virtualenvwrapper.readthedocs.io/en/stable/) to simplify the management of multiple venvs. Once created, activate the virtual environment: @@ -99,12 +107,13 @@ Within the `netbox/netbox/` directory, copy `configuration_example.py` to `confi Django provides a lightweight, auto-updating HTTP/WSGI server for development use. It is started with the `runserver` management command: ```no-highlight -$ python netbox/manage.py runserver +$ ./manage.py runserver +Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). -November 18, 2020 - 15:52:31 -Django version 3.1, using settings 'netbox.settings' +February 18, 2022 - 20:29:57 +Django version 4.0.2, using settings 'netbox.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ``` @@ -122,24 +131,36 @@ The demo data is provided in JSON format and loaded into an empty database using ## Running Tests -Prior to committing any substantial changes to the code base, be sure to run NetBox's test suite to catch any potential errors. Tests are run using the `test` management command. Remember to ensure the Python virtual environment is active before running this command. Also keep in mind that these commands are executed in the `/netbox/` directory, not the root directory of the repository. +Prior to committing any substantial changes to the code base, be sure to run NetBox's test suite to catch any potential errors. Tests are run using the `test` management command, which employs Python's [`unittest`](https://docs.python.org/3/library/unittest.html#module-unittest) library. Remember to ensure the Python virtual environment is active before running this command. Also keep in mind that these commands are executed in the `netbox/` directory, not the root directory of the repository. + +To avoid potential issues with your local configuration file, set the `NETBOX_CONFIGURATION` to point to the packaged test configuration at `netbox/configuration_testing.py`. This will handle things like ensuring that the dummy plugin is enabled for comprehensive testing. ```no-highlight +$ export NETBOX_CONFIGURATION=netbox.configuration_testing +$ cd netbox/ $ python manage.py test ``` -In cases where you haven't made any changes to the database (which is most of the time), you can append the `--keepdb` argument to this command to reuse the test database between runs. This cuts down on the time it takes to run the test suite since the database doesn't have to be rebuilt each time. (Note that this argument will cause errors if you've modified any model fields since the previous test run.) +In cases where you haven't made any changes to the database schema (which is typical), you can append the `--keepdb` argument to this command to reuse the test database between runs. This cuts down on the time it takes to run the test suite since the database doesn't have to be rebuilt each time. (Note that this argument will cause errors if you've modified any model fields since the previous test run.) ```no-highlight $ python manage.py test --keepdb ``` -You can also limit the command to running only a specific subset of tests. For example, to run only IPAM and DCIM view tests: +You can also reduce testing time by enabling parallel test execution with the `--parallel` flag. (By default, this will run as many parallel tests as you have processors. To avoid sluggishness, it's a good idea to specify a lower number of parallel tests.) This flag can be combined with `--keepdb`, although if you encounter any strange errors, try running the test suite again with parallelization disabled. + +```no-highlight +$ python manage.py test --parallel +``` + +Finally, it's possible to limit the run to a specific set of tests, specified by their Python path. For example, to run only IPAM and DCIM view tests: ```no-highlight $ python manage.py test dcim.tests.test_views ipam.tests.test_views ``` +This is handy for instances where just a few tests are failing and you want to re-run them individually. + ## Submitting Pull Requests Once you're happy with your work and have verified that all tests pass, commit your changes and push it upstream to your fork. Always provide descriptive (but not excessively verbose) commit messages. When working on a specific issue, be sure to prefix your commit message with the word "Fixes" or "Closes" and the issue number (with a hash mark). This tells GitHub to automatically close the referenced issue once the commit has been merged. diff --git a/docs/media/development/github_fork_button.png b/docs/media/development/github_fork_button.png new file mode 100644 index 0000000000000000000000000000000000000000..b326d5de11af3f5d5b7536c0d8a07e58e3bcc13e GIT binary patch literal 1954 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

HeGR?80q%Xn;>O0oX~y&FfPf_9HAY^qAx6I> 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 inwJQwM$8D~IKp2v8!|77|Nr9v0000