Skip to content

Commit 52d7e46

Browse files
committed
General cleanup
1 parent d8d3a4d commit 52d7e46

10 files changed

+152
-132
lines changed

tutorial/step01-initial-setup.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,7 @@ Back in the project's root (one level up from `__init__.py`), create a file name
7676
Manage simple access control lists in NetBox
7777
```
7878

79-
You'll notice that our reference repo names this file `README.md`: The `md` extension tells tools which support it to render the file as Markdown for better readability.
80-
81-
:warning: **Warning:** Be sure to create this file in the project root and _not_ within the `netbox_access_lists` directory.
79+
:green_circle: **Tip:** You'll notice that we've given our `README` file a `md` extension. This tells tools which support it to render the file as Markdown for better readability.
8280

8381
## Install the Plugin
8482

@@ -93,17 +91,16 @@ setup(
9391
name='netbox-access-lists',
9492
version='0.1',
9593
description='An example NetBox plugin',
96-
license='Apache 2.0',
9794
install_requires=[],
9895
packages=find_packages(),
9996
include_package_data=True,
10097
zip_safe=False,
10198
)
10299
```
103100

104-
:warning: **Warning:** Be sure to create this file in the project root and _not_ within the `netbox_access_lists` directory.
101+
:warning: **Warning:** Be sure to create `setup.py` in the project root and _not_ within the `netbox_access_lists` directory.
105102

106-
This file will call the `setup()` function provided by `setuptools` to install our code. There are plenty of additional arguments that can be passed, but for our example this is sufficient.
103+
This file will call the `setup()` function provided by Python's [`setuptools`](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/) library to install our code. There are plenty of additional arguments that can be passed, but for our example this is sufficient.
107104

108105
:green_circle: **Tip:** There are alternative methods for installing Python code which work just as well; free free to use your preferred approach. Just be aware that this guide assumes the use of `setuptools` and adjust accordingly.
109106

@@ -146,7 +143,7 @@ Save the file and run the NetBox development server (if not already running):
146143
$ python netbox/manage.py runserver
147144
```
148145

149-
You should see the development start successfully. Open NetBox in a new browser window, log in as a superuser, and navigate to the admin UI. Under **System > Installed Plugins** you should see our plugin listed.
146+
You should see the development server start successfully. Open NetBox in a new browser window, log in as a superuser, and navigate to the admin UI. Under **System > Installed Plugins** you should see our plugin listed.
150147

151148
![Django admin UI: Plugins list](/images/step01-django-admin-plugins.png)
152149

tutorial/step02-models.md

+36-25
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ from netbox.models import NetBoxModel
2424
We'll create two models:
2525

2626
* `AccessList`: This will represent an access list, with a name and one or more rules assigned to it.
27-
* `AccessListRule`: This will be an individual rule with source/destination IP addresses and port numbers, etc. assigned to an access list.
27+
* `AccessListRule`: This will be an individual rule with source/destination IP addresses, port numbers, etc. assigned to an access list.
2828

2929
### AccessList
3030

@@ -43,14 +43,14 @@ class AccessList(NetBoxModel):
4343
)
4444
```
4545

46-
By default, model instances are ordered by their primary keys, but it would make more sense to order access lists by name. We can do that by creating a `Meta` subclass and defining an `ordering` variable. (Be sure to create the `Meta` class *inside* `AccessList`, not under it.)
46+
By default, model instances are ordered by their primary keys, but it would make more sense to order access lists by name. We can do that by creating a `Meta` child class and defining an `ordering` variable. (Be sure to create the `Meta` class *inside* `AccessList`, not after it.)
4747

4848
```python
4949
class Meta:
5050
ordering = ('name',)
5151
```
5252

53-
Finally, we'll add a `__str__()` method to control how an instance is rendered in a string. We'll have this return the value of the instance's `name` field. (Again, be sure to create this *inside* `AccessList`.)
53+
Finally, we'll add a `__str__()` method to control how an instance is coerced to a string. We'll have this return the value of the instance's `name` field. (Again, be sure to create this method *inside* the `AccessList` class.)
5454

5555
```python
5656
def __str__(self):
@@ -59,9 +59,9 @@ Finally, we'll add a `__str__()` method to control how an instance is rendered i
5959

6060
### AccessListRule
6161

62-
Our second model will hold the individual rules assigned to each access list. This model will be a bit more complex. We'll need to define fields for:
62+
Our second model will hold the individual rules assigned to each access list. This model will be a bit more complex. We'll need to define fields for all of the following:
6363

64-
* Parent access list (pointing to and `AccessList` instance)
64+
* Parent access list (a foreign key to an `AccessList` instance)
6565
* Index (the rule's order in the list)
6666
* Protocol
6767
* Source prefix
@@ -84,17 +84,17 @@ class AccessListRule(NetBoxModel):
8484

8585
We're passing three keyword arguments to the field:
8686

87-
* `to` references the related model class (this can alternatively be the _name_ of the class)
88-
* `on_delete` tells Django what action to take if the related object is deleted. `CASCADE` will automatically delete any rules assigned to it as well.
89-
* `related_name` defines the attribute of the reverse relationship being added to the related class. The rule assigned to an `AccessList` instance can be referenced as `accesslist.rules.all()`.
87+
* `to` references the related model class
88+
* `on_delete` tells Django what action to take if the related object is deleted. `CASCADE` will automatically delete any rules assigned to a deleted access list.
89+
* `related_name` defines the attribute of the reverse relationship being added to the related class. The rules assigned to an `AccessList` instance can be referenced as `accesslist.rules.all()`.
9090

9191
Next we'll add an `index` field to store the rule's number (position) within the access list. We'll use `PositiveIntegerField` because only positive numbers are supported.
9292

9393
```python
9494
index = models.PositiveIntegerField()
9595
```
9696

97-
The protocol field is next. This will store the name of a protocol such as `'tcp'` or `'udp'`. Notice that we're setting `blank=True` because it should not be required to specify a particular protocol when creating a rule.
97+
The protocol field is next. This will store the name of a protocol such as TCP or UDP. Notice that we're setting `blank=True` because it should not be required to specify a particular protocol when creating a rule.
9898

9999
```python
100100
protocol = models.CharField(
@@ -103,9 +103,7 @@ The protocol field is next. This will store the name of a protocol such as `'tcp
103103
)
104104
```
105105

106-
:green_circle: **Tip:** Why didn't we set `null=True` like we did for the previous optional fields? Because this is a `CharField`, it's recommended to store empty values as empty strings rather than `null`. For other data types, like integers or booleans, `null` must be explicitly allowed at the database level for optional fields.
107-
108-
Next we need to define a source prefix. We're going to use a foreign key field to reference an instance of NetBox's `Prefix` model within its `ipam` app. Instead of importing the model class, we can just reference it by its name. And because we want this to be an _optional_ field, we'll also set `blank=True` and `null=True`.
106+
Next we need to define a source prefix. We're going to use a foreign key field to reference an instance of NetBox's `Prefix` model within its `ipam` app. Instead of importing the model class, we can instead reference it by name. And because we want this to be an _optional_ field, we'll also set `blank=True` and `null=True`.
109107

110108
```python
111109
source_prefix = models.ForeignKey(
@@ -117,6 +115,8 @@ Next we need to define a source prefix. We're going to use a foreign key field t
117115
)
118116
```
119117

118+
:green_circle: **Tip:** Whereas `CASCADE` automatically deletes child objects, `PROTECT` prevents the deletion of the parent option if any child objects exist.
119+
120120
Notice above that we've defined `related_name='+'`. This tells Django not to create a reverse relationship from the `Prefix` model to the `AccessListRule` model, because it wouldn't be very useful.
121121

122122
We also need to add a field for the source port number(s). We could use an integer field for this, however that would limit us to defining a single source port per rule. Instead, we can add an `ArrayField` to store a list of `PositiveIntegerField` values. Like `source_prefix`, this will also be an optional field, so we add `blank=True` and `null=True` as well.
@@ -166,7 +166,7 @@ With our fields out of the way, this model will also need a `Meta` class to defi
166166
unique_together = ('access_list', 'index')
167167
```
168168

169-
Finally, we'll add a `__str__()` method to display the parent access list and index number when rendering an instance as a string:
169+
Finally, we'll add a `__str__()` method to display the parent access list and index number when rendering an `AccessListRule` instance as a string:
170170

171171
```python
172172
def __str__(self):
@@ -181,7 +181,7 @@ Looking back at our models, we see a few fields that would benefit from having p
181181
* Deny
182182
* Reject
183183

184-
We can define a `ChoiceSet` to store these pre-defined values for the user, to avoid the hassle of manually typing the name of the desired value each time. Back at the top of `models.py`, import NetBox's `ChoiceSet` class:
184+
We can define a `ChoiceSet` to store these pre-defined values for the user, to avoid the hassle of manually typing the name of the desired action each time. Back at the top of `models.py`, import NetBox's `ChoiceSet` class:
185185

186186
```python
187187
from utilities.choices import ChoiceSet
@@ -200,15 +200,15 @@ class ActionChoices(ChoiceSet):
200200
)
201201
```
202202

203-
The `CHOICES` attribute is a tuple of tuples, each of which holds three values:
203+
The `CHOICES` attribute must be an iterable of two- or three-value tuples, each of which defines the following:
204204

205205
* The raw value to be stored in the database
206206
* A human-friendly string for display
207207
* A color for display in the UI (optional)
208208

209-
Additionally, we've added a `key` attribute: This will allow the NetBox administrator to replace or extend the plugin's default choices here with his or her own values.
209+
Additionally, we've added a `key` attribute: This will allow the NetBox administrator to replace or extend the plugin's default choices via NetBox's [`FIELD_CHOICES`](https://netbox.readthedocs.io/en/feature/configuration/optional-settings/#field_choices) configuration parameter.
210210

211-
Now, we can reference this as the set of valid choices on the `default_action` and `action` model fields by referencing it with the `choices` keyword argument.
211+
Now, we can reference this as the set of valid choices on the `default_action` and `action` model fields by passing it as the `choices` keyword argument.
212212

213213
```python
214214
# AccessList
@@ -224,7 +224,7 @@ Now, we can reference this as the set of valid choices on the `default_action` a
224224
)
225225
```
226226

227-
Let's create a set of choices for the rule model's `protocol` field as well. Add this below the `ActionChoices` class:
227+
Let's create a set of choices for a rule's `protocol` field as well. Add this below the `ActionChoices` class:
228228

229229
```python
230230
class ProtocolChoices(ChoiceSet):
@@ -249,13 +249,13 @@ Then, add the `choices` keyword argument to the `protocol` field:
249249

250250
## Create Schema Migrations
251251

252-
Now that we have our models defined, we need to generate a schema for the PostgreSQL database. While it's possible to create the tables and constraints by hand, it's _much_ easier to employ Django's [migrations feature](https://docs.djangoproject.com/en/4.0/topics/migrations/), which will introspect our model classes and generate the necessary migration files automatically. This is a two-step process: First we generate the migration file with the `makemigrations` management command, then we run `migrate` to apply it to the live database.
252+
Now that we have our models defined, we need to generate a schema for the PostgreSQL database. While it's possible to create the tables and constraints by hand, it's _much_ easier to employ Django's [migrations feature](https://docs.djangoproject.com/en/4.0/topics/migrations/). This will inspect our model classes and generate the necessary migration files automatically. This is a two-step process: First we generate the migration file with the `makemigrations` management command, then we run `migrate` to apply it to the live database.
253253

254254
:warning: **Warning:** Before continuing, check that you've set `DEVELOPER=True` in NetBox's `configuration.py` file. This is necessary to disable a safeguard intended to prevent people from creating new migrations mistakenly.
255255

256256
### Generate Migration Files
257257

258-
Change into the NetBox installation root to run `manage.py`. First, we'll run `makemigrations` with the `--dry-run` argument as a sanity-check: This will report what changes have been detected, but won't actually generate any migration files.
258+
Change into the NetBox installation root to run `manage.py`. First, we'll run `makemigrations` with the `--dry-run` argument as a sanity-check. This will report what changes have been detected, but won't actually generate any migration files.
259259

260260
```bash
261261
$ python netbox/manage.py makemigrations netbox_access_lists --dry-run
@@ -324,12 +324,14 @@ Referenced by:
324324
TABLE "netbox_access_lists_accesslistrule" CONSTRAINT "netbox_access_lists__access_list_id_6c1b0317_fk_netbox_ac" FOREIGN KEY (access_list_id) REFERENCES netbox_access_lists_accesslist(id) DEFERRABLE INITIALLY DEFERRED
325325
```
326326

327+
Type `\q` to exit `dbshell`.
328+
327329
## Create Some Objects
328330

329-
Now that we have our models installed, let's try creating some objects. First, enter the NetBox shell. This is an interactive Python command line which allows us to interact directly with NetBox objects and other resources.
331+
Now that we have our models installed, let's try creating some objects. First, enter the NetBox shell. This is an interactive Python command line interface which allows us to interact directly with NetBox objects and other resources.
330332

331333
```bash
332-
$ ./manage.py nbshell
334+
$ python netbox/manage.py nbshell
333335
from netbox### NetBox interactive shell
334336
### Python 3.8.12 | Django 4.0.3 | NetBox 3.2.0
335337
### lsmodels() will show available models. Use help(<model>) for more info.
@@ -346,7 +348,16 @@ Let's create and save an access list:
346348
>>> acl.save()
347349
```
348350

349-
And a few rules to go with it:
351+
Next we'll create some prefixes to reference in rules:
352+
353+
```python
354+
>>> prefix1 = Prefix(prefix='192.168.1.0/24')
355+
>>> prefix1.save()
356+
>>> prefix2 = Prefix(prefix='192.168.2.0/24')
357+
>>> prefix2.save()
358+
```
359+
360+
And finally we'll create a couple rules for our access list:
350361

351362
```python
352363
>>> AccessListRule(
@@ -361,7 +372,7 @@ And a few rules to go with it:
361372
>>> AccessListRule(
362373
... access_list=acl,
363374
... index=20,
364-
... protocol='dns',
375+
... protocol='udp',
365376
... destination_prefix=prefix2,
366377
... destination_ports=[53],
367378
... action='permit',
@@ -371,7 +382,7 @@ And a few rules to go with it:
371382
<RestrictedQuerySet [<AccessListRule: MyACL1: Rule 10>, <AccessListRule: MyACL1: Rule 20>]>
372383
```
373384

374-
Excellent! We can now create access lists and rules in the database. The next few steps will work on expsoing this functionality in the NetBox user interface.
385+
Excellent! We can now create access lists and rules in the database. The next few steps will work on exposing this functionality in the NetBox user interface.
375386

376387
<div align="center">
377388

0 commit comments

Comments
 (0)