Nautobot Apps - Lab 05: Extending the Navigation Menu & Capturing the Username¶
So far, we've built a maintenance notice model and views for retrieving, creating, updating, and deleting instances of that model. Our app is really coming along! However, it's still not very tightly integrated with the rest of Nautobot. In this lab, we'll explore how we can extend Nautobot's navigation menu to link to our plugin's views, and even embed plugin content into core Nautobot views.
- Nautobot Apps - Lab 05: Extending the Navigation Menu \& Capturing the Username
- Task 1: Extending the Nautobot Navigation Menu
- Task 2: Restrict Access to Maintenance Notices
- Step 2-1 - Add Permissions
- Step 2-2 - Add Read Only Permissions
- Step 2-3 - Add Read/Write Permissions
- Step 2-4 - Check Additional Permissions
- Step 2-5 - Grant Additional Permissions
- Step 2-6 - Reset Passwords
- Step 2-7 - Test Read/Write Access for User2
- Step 2-8 - Test Read Access for User1
- Step 2-9 - Hide the Add Button
- Step 2-10 - Verify the Button is Removed
- Step 2-11 - Restrict Access to the Menu Item
- Step 2-12 - Remove View Permission from User1
- Step 2-13 - Verify the Menu Item View Restriction
- Task 3: Add a DateTimePicker Widget
- Task 4: Associate a User to the Maintenance Notice
- Step 4-1 - Create a Maintenance Notice via Web Browser
- Step 4-2 - Split the Create and Edit Views
- Step 4-3 - Update the
urls.pyfile - Step 4-4 - Verify the Views
- Step 4-5 - Check the Nautobot Source Code
- Step 4-6 - Modify the Model object from the View
- Step 4-7 - Enter the Debugger
- Step 4-8 - Explore the Method Arguments
- Step 4-9 - Assign the User to the Maintenance Notice
- Step 4-10 - Commit Your Changes
Task 1: Extending the Nautobot Navigation Menu¶
To better integrate with Nautobot, we're going to add a link in the navigation menu to view all maintenance notices, as well as a menu button to add a new notice. This is consistent with how the core Nautobot objects are arranged within the menu.
Step 1-0 - Prepare for the Lab¶
For this and all remaining labs, it is advisable to open two terminal windows, one for running the Nautobot development server, and another for executing various commands.
The steps in this lab require that you have a terminal open and have switched to the nautobot user. Verify your username with the whoami command and switch user to nautobot if needed.
ntc@nautobot:~$ whoami
ntc
ntc@nautobot:~$ sudo -iu nautobot
nautobot@nautobot:~$ whoami
nautobot
nautobot@nautobot:~$
If you have the Nautobot development server running from a previous lab, please continue to the next step. Otherwise we need to start the server with the command nautobot-server runserver 0.0.0.0:8080 --insecure.
nautobot@ntc-nautobot-apps:~$ nautobot-server runserver 0.0.0.0:8080 --insecure
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
April 24, 2022 - 05:19:10
Django version 3.1.14, using settings 'nautobot_config'
Starting development server at http://0.0.0.0:8080/
Quit the server with CONTROL-C.
Step 1-1 - Create the navigation.py file¶
Nautobot looks for a navigation.py module within each plugin to check for any menu extensions. Locate and edit this file. We will need to import some classes that will be used to create the menu items and buttons. Your file should include these lines near the top:
from nautobot.extras.plugins import PluginMenuButton, PluginMenuItem
from nautobot.utilities.choices import ButtonColorChoices
Define a tuple named menu_items to hold all our menu extensions. The file should already contain some example code that we can edit to meet our needs.
Step 1-2 - Create a Menu Item¶
Custom menu items are added by instantiating Nautobot's PluginMenuItem class. There are four arguments we can pass when doing so:
link: The name of the URL path to us for this linklink_text: The menu item textpermissions: A list of permissions required to display this link (optional)buttons: An iterable of embedded menu buttons to include (optional)
Let's add a link to our maintenance notice list view. First, import extras.plugins.PluginMenuItem, then add an instance to menu_items. We don't need to define any required permissions for this item, because everyone should be able to view maintenance notices.
menu_items = (
PluginMenuItem(
link='plugins:maintenance_notices:maintenancenotice_list',
link_text='Maintenance Notices',
),
)
Notice that the link is set to plugins:maintenance_notices:maintenancenotice_list. This string is a made of three parts that are each separated by a colon (:). This tells Nautobot how to locate the link: look in plugins: look in our maintenance_notices plugin, and finally, find the maintenancenotice_list link. Recall that maintenancenotice_list refers to a specific URL route that we defined in the urls.py file.
Save the file.
Step 1-3 - Access the Menu Item¶
Your nautobot-server should detect the changed file and reload. Take a look at the terminal session where you are running your server to check that it reloaded successfully.
Note: If your
nautobot-serverdisplaysTypeError: 'PluginMenuItem' object is not iterable, then you are likely missing a comma after the parenthesis enclosing yourPluginMenuItem.
Load the Nautobot home page in your browser. If you already had this page open, hit the refresh button.
Click the "Plugins" dropdown menu and you should now see a Maintenance Notices section near the bottom of the drop-down menu. Within that section should be our Maintenance Notices link. Click the link and you'll be taken to the maintenance notices list.

You should see the Maintenance Notices list view page.

This is a lot easier than pasting in the link every time you want to access this page.
Step 1-4 - Create a Menu Button¶
Next, we'll add a button within this menu item to create new notices. To do this we will employ Nautobot's PluginMenuButton class, as well as ButtonColorChoices. The later is a list of valid button colors that we'll reference when creating the button.
There are several parameters we can pass when instantiating PluginMenuButton to control how buttons are displayed:
link: The name of the URL path to us for this buttontitle: The tooltip text (displayed when the mouse hovers over the button)icon_class: Button icon CSS classcolor: One of the choices provided byButtonColorChoices(optional)permissions: A list of permissions required to display this button (optional)
The link, title, and icon class parameters are required. The icon class is a CSS icon passed as a string; for example, mdi mdi-plus-thick. In this example, the mdi represents Google's "Material Design Icons" and the mdi-plus-thick is a plus sign from that collection.
Create a new PluginMenuButton instance above menu_items in the navigation.py file. We'll use the mdi-plus-thick icon CSS class and the color GREEN. We'll also restrict the display of this button to users who possess the add_maintenancenotice permission.
from nautobot.extras.plugins import PluginMenuButton, PluginMenuItem
from nautobot.utilities.choices import ButtonColorChoices
add_maintenancenotice_button = PluginMenuButton(
link='plugins:maintenance_notices:maintenancenotice_add',
title='Add a new maintenance notice',
icon_class='mdi mdi-plus-thick',
color=ButtonColorChoices.GREEN,
)
menu_items = (...)
Step 1-5 - Add a Button to a Menu Item¶
Now that we have our button defined, add it to the menu item by appending the buttons keyword argument to the PluginMenuItem instance:
menu_items = (
PluginMenuItem(
link='plugins:maintenance_notices:maintenancenotice_list',
link_text='Maintenance Notices',
buttons=[add_maintenancenotice_button],
),
)
Save the file.
Step 1-6 - Check Your Work¶
Refresh the Nautobot home page again. Click the Plugins drop-down menu. Next to the Maintenance Notices link you'll see a small green button with a + icon.

Click the + button to visit the Maintenance Notice creation view.

Task 2: Restrict Access to Maintenance Notices¶
In this task, we'll define permissions that restrict which users can create maintenance notices.
Back in Lab 3, when we created a new model for our plugin in models.py, we imported the Nautobot BaseModel class and inherited its behaviors. This was performed via these two lines:
One of the features provided by the BaseModel class is the automatic creation of a set of permission objects that allow us to control CRUD access to the data represented by our model MaintenanceNotice. These are the permissions that are automatically created for our model, along with the corresponding CRUD operations:
add_maintenancenotice # Create
view_maintenancenotice # Read
change_maintenancenotice # Update
delete_maintenancenotice # Delete
Up to this point we have been logging into Nautobot web console using the admin user account, which has superuser access to the platform.
Step 2-1 - Add Permissions¶
Using the Nautobot administrative interface, we will add two permissions to Nautobot. If you have not already done so, open Nautobot in your web browser and login as the admin user. Click the admin username in the top right hand corner, then click Admin.

Navigate to the Users section toward the bottom of the page and click the +Add button next to permissions.

Step 2-2 - Add Read Only Permissions¶
First we will create a read only permission for our plugin model. Fill out the form with the following information:
Name:maintenance_notices_read_onlyEnabled: Check boxActions: Check box forCan viewonly (This is ourReadaccess permission)Object types: Locatemaintenance_notices > Maintenance Noticein the list and click it.Users: Locateuser1and click.



At the bottom of the page, click Save and add another.

You should see a banner across the top stating The permission “maintenance_notices_read_only” was added successfully. You may add another permission below..

Step 2-3 - Add Read/Write Permissions¶
Create a second permission to allow the full read/write access to our model. This permission will be assigned to user2.
Name:maintenance_notices_read_writeEnabled: Check boxActions: Check boxes for all four actions:Can viewCan addCan changeCan deleteObject types: Locatemaintenance_notices > Maintenance Noticein the list and click it.Users: Locateuser2and click.
Click the Save button. This will take you to the Permission list view page. Our new permissions should be displayed toward the bottom of the page.

Step 2-4 - Check Additional Permissions¶
Lets make sure our user1 and user2 have access to dcim.Device objects. Navigate to the Admin site by clicking your user name and then clicking Admin. Alternatively, you can use the "breadcrumbs" in the left side banner to click Admin Home.
Click either Admin Home on the left or Admin on the right.

Click the link for Users under the Users section.

Click the link for user1.

Scroll down to the bottom of the page to find Permissions. Note that user1 has the demo_read_only permission that provides view access to the device model. Since this is what we want, we do not need to change anything here.

Step 2-5 - Grant Additional Permissions¶
Use the "breadcrumbs" at the top of the page to navigate back to the users page.

Click the link for user2.

Scroll down to the bottom of the page to find Permissions. Click the drop-down in the first available slot (---------). Then select the demo permission. Click Save to save you change.

Step 2-6 - Reset Passwords¶
Now that we have defined these two permissions and assigned them to different users, we can test access for each of the users by logging out of the admin account and logging in, one at a time, to the user1 and user2 accounts.
If you do not know the passwords to these user accounts you can reset them through the admin page or you can use the command line to reset the passwords.
Set the user1 account password to one of your choosing:
nautobot@nautobot:~/plugin$ nautobot-server changepassword user1
Changing password for user 'user1'
Password:
Password (again):
Password changed successfully for user 'user1'
nautobot@nautobot:~/plugin$
Let's do the same for the user2 account:
nautobot@nautobot:~/plugin$ nautobot-server changepassword user2
Changing password for user 'user2'
Password:
Password (again):
Password changed successfully for user 'user2'
nautobot@nautobot:~/plugin$
Step 2-7 - Test Read/Write Access for User2¶
Let's start with user2.
Open Nautobot in your web browser and logout of the current account.
Now login as user2. Remember we set user2 to have read/write access. From the navigation menu, click Plugins > Maintenance Notices. You should see the ListView page for Maintenance Notices.

Note that this page includes a blue +Add button in the upper right hand corner. Click the +Add button to verify that you arrive at the Add a new Maintenance Notice page.

This confirms that we have the correct permissions. You can use this page to add a new maintenance notice to be sure.
Step 2-8 - Test Read Access for User1¶
In your web browser, logout, and then login as user1.
Recall we set user1 to have read only access. From the navigation menu, click Plugins > Maintenance Notices. This should again bring up the ListView page for Maintenance Notices. Note this time that this page does not display the +Add button that was previously seen in the upper right hand corner.

You may have noticed that when we click Plugins from the navigation menu, the the + (Add) button is showing up next to Maintenance Notices on the dropdown menu. Click the + to attempt to access the Add page.

You should be greeted with an error Access Denied: You do not have permission to access this page.

Step 2-9 - Hide the Add Button¶
Ideally, we only want to display an Add button on the menu when the logged in user has the add_maintenancenotice permission object.
Edit the navigation.py file and locate where we defined add_maintenancenotice_button.
After the line:
Add this line to require the add permission object:
Note: When adding
permissionsargument, make sure the previous line ends with a comma. When your arguments are formatted on separate lines it is good practice to end all lines, including the last one, with a comma, as it minimizes the number of lines changed in your change control system and prevents errors from missing commas when a new argument is added.
The file should now look like this:
"""Navigation Items to add to Nautobot for maintenance_notices."""
from nautobot.extras.plugins import PluginMenuButton, PluginMenuItem
from nautobot.utilities.choices import ButtonColorChoices
add_maintenancenotice_button = PluginMenuButton(
link='plugins:maintenance_notices:maintenancenotice_add',
title='Add a new maintenance notice',
icon_class='mdi mdi-plus-thick',
color=ButtonColorChoices.GREEN,
permissions=['maintenance_notices.add_maintenancenotice'],
)
menu_items = (
PluginMenuItem(
link='plugins:maintenance_notices:maintenancenotice_list',
link_text='Maintenance Notices',
buttons=[add_maintenancenotice_button],
),
)
Save the file and refresh your browser.
Step 2-10 - Verify the Button is Removed¶
If you are not logged in as read_only user1, login as that user. Again, click Plugins from the navigation menu and note that the + (Add) button has been removed from the Maintenance Notices menu item.

Step 2-11 - Restrict Access to the Menu Item¶
Let's add the same permissions argument to the PluginMenuItem.
Edit the navigation.py files and add the line permissions=['maintenance_notices.view_maintenancenotice'], into argument list for the PluginMenuItem:
menu_items = (
PluginMenuItem(
link='plugins:maintenance_notices:maintenancenotice_list',
link_text='Maintenance Notices',
buttons=[add_maintenancenotice_button],
permissions=['maintenance_notices.view_maintenancenotice'],
),
)
Save the file.
Step 2-12 - Remove View Permission from User1¶
In your web browser, logout of Nautobot and the log in as the admin user.
Navigate to the Admin site by clicking your user name and then the Admin link. Click Users at the bottom of the page. This should bring you to the user list. Click the link for user1.

Scroll to the bottom of the page to the Permissions section. Locate the permission for maintenance_notices_read_only and select the checkbox located in the Delete? column. Then click the Save button. This will remove user1's view (read) permission to our app.

Step 2-13 - Verify the Menu Item View Restriction¶
Once again, in your web browser, logout, and then login as user1.
Now when you click on the Plugins menu, the Maintenance Notices menu item is grayed out and cannot be clicked. This is what we want to happen for users that do not have read (view) access to the plugin.

Task 3: Add a DateTimePicker Widget¶
Step 3-1 - Check the Current Behavior¶
Login to Nautobot with the admin user account. Navigate to Plugins > Maintenance Notices and click the +Add button.
Fill out the form. Notice that the Start Time value requires that a valid date and time must be entered. Nautobot includes utilities that aid us when building forms that are simple to fill out. We will add the DateTimePicker to our form to simplify choosing the start time.
Step 3-2 - Edit the forms.py file¶
Edit the forms.py file.
Add the DateTimePicker into your imports.
"""Forms for maintenance_notices."""
from django import forms
from nautobot.utilities.forms import (
BootstrapMixin,
DateTimePicker,
)
Locate the MaintenanceNoticeForm class definition. Between the docstring and the class Meta:, insert the following code:
start_time = forms.DateTimeField(
widget=DateTimePicker(attrs={"placeholder": "Maintenance Start Date and Time"}),
label="Start Time",
)
Step 3-3 - Verify the forms.py file¶
Verify that your forms.py file should look like this the following snippet and save your file.
"""Forms for maintenance_notices."""
from django import forms
from nautobot.utilities.forms import (
BootstrapMixin,
DateTimePicker,
)
from maintenance_notices import models
class MaintenanceNoticeForm(BootstrapMixin, forms.ModelForm):
"""MaintenanceNotice creation/edit form."""
start_time = forms.DateTimeField(
widget=DateTimePicker(attrs={"placeholder": "Maintenance Start Date and Time"}),
label="Start Time",
)
class Meta:
"""Meta attributes."""
model = models.MaintenanceNotice
fields = ("__all__")
Step 3-4 - Test the DateTimePicker Widget¶
Navigate to Plugins > Maintenance Notices > Add to add a new Maintenance Notice. Click on the Start Time field. Notice the form now provides an easy way to select the date and time.

Task 4: Associate a User to the Maintenance Notice¶
Step 4-1 - Create a Maintenance Notice via Web Browser¶
Login to Nautobot with the admin user account. Navigate to Plugins > Maintenance Notices and click the +Add button.
On the Add a new Maintenance Notice page, Fill out the Start time, Duration, and select one or more devices. Click the Create button.

If there were any errors while submitting the form, they will be flagged and you will return to the creation view. Otherwise, you should be automatically directed to the newly created maintenance notice.

Note that the Created By attribute of our new maintenance notice is empty, represented by a line, where we expect to see the name of the logged in user. This is because we never added the logic for assigning the authenticated user to the maintenance notice when it's created.
Step 4-2 - Split the Create and Edit Views¶
Currently we are using the same view (MaintenanceNoticeCreateView) for both the Create and Edit forms, which has been fine up to the point. However, we only want to set the created_by field when we first create the our notice and we do not want to overwrite it when someone edits the record.
Edit the views.py file and locate the MaintenanceNoticeCreateView.
class MaintenanceNoticeCreateView(generic.ObjectEditView):
"""Create view."""
model = models.MaintenanceNotice
queryset = models.MaintenanceNotice.objects.all()
model_form = forms.MaintenanceNoticeForm
Since we do not plan to change the Edit view, but we do want to modify the create view, lets rename this class to MaintenanceNoticeEditView and create a new MaintenanceNoticeCreateView that inherits from the previous view.
class MaintenanceNoticeEditView(generic.ObjectEditView):
"""Edit view."""
model = models.MaintenanceNotice
queryset = models.MaintenanceNotice.objects.all()
model_form = forms.MaintenanceNoticeForm
class MaintenanceNoticeCreateView(MaintenanceNoticeEditView):
"""Create view."""
pass
Note that
MaintenanceNoticeCreateViewis now a subclass ofMaintenanceNoticeEditViewand soMaintenanceNoticeEditViewmust precedeMaintenanceNoticeCreateView.
Save the file.
Step 4-3 - Update the urls.py file¶
It is not enough to create the new Edit view, we need to update the path in our urls.py file. Edit urls.py and locate the path ending in edit. Change view to use the MaintenanceNoticeEditView
path("maintenancenotice/<uuid:pk>/edit/", views.MaintenanceNoticeEditView.as_view(), name="maintenancenotice_edit"),
Save the file.
Step 4-4 - Verify the Views¶
Create a new maintenance notice and verify that the behavior has not changed.
Navigate to Plugins > Maintenance Notices. Click the edit icon, which is the one that looks like a pencil, for the first maintenance notice on the list.

This should allow you to edit the chosen maintenance notice. Make a change to the duration and click the Update button. Verify that the behavior is unchanged.
Step 4-5 - Check the Nautobot Source Code¶
The ObjectEditView class provides a built in method named alter_obj that can be used for this purpose. If we take a look at the source code, we see that the alter_obj method accepts an object named obj and several arguments and then simply returns the object that was passed to it. This method then gets called by the view's get method. From this we can see that alter_obj is a method that is intended as a hook for us to replace with our own method in our subclass, and this will allow us to modify the object that is presented to the form.
Nautobot ObjectEditView source code
def alter_obj(self, obj, request, url_args, url_kwargs):
# Allow views to add extra info to an object before it is processed. For example, a parent object can be defined
# given some parameter from the request URL.
return obj
...
def get(self, request, *args, **kwargs):
obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
...
Step 4-6 - Modify the Model object from the View¶
Edit the views.py file and locate the MaintenanceNoticeCreateView.
Remove the line pass and add the alter_obj method definition as show below.
class MaintenanceNoticeCreateView(MaintenanceNoticeEditView):
"""Create view."""
def alter_obj(self, obj, request, *args, **kwargs):
"""Insert user into object."""
import pdb; pdb.set_trace()
return obj
Notice that we have inserted a debugger into the method. The method otherwise doesn't do anything but when the code hits this point, our console should provide display the Pdb prompt.
Step 4-7 - Enter the Debugger¶
Save the file and wait for the server to reload.
Now use the web browser to add another maintenance notice. Upon clicking the +Add button the browser should pause as if the page is not loading. This is normal as the debugger has paused execution of the server.
Open the terminal where you are running the nautobot-server. You terminal should be waiting at the (Pdb) prompt as shown below.
/opt/nautobot/plugin/maintenance_notices/views.py changed, reloading.
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
March 10, 2022 - 23:38:53
Django version 3.1.14, using settings 'nautobot_config'
Starting development server at http://0.0.0.0:8080/
Quit the server with CONTROL-C.
> /opt/nautobot/plugin/maintenance_notices/views.py(36)alter_obj()
-> return obj
(Pdb)
Step 4-8 - Explore the Method Arguments¶
We now have the (Pdb) prompt that we saw in an earlier lab. We know that two named arguments are passed to the get_alter method: obj, and request. Let's look at obj first.
(Pdb) obj
*** TypeError: unsupported format string passed to NoneType.__format__
(Pdb) type(obj)
<class 'maintenance_notices.models.MaintenanceNotice'>
We can see that in this case obj is an instance of our MaintenanceNotice model.
Let's look into the request argument. Type dir(request) to see the attributes of the request object that was passed into this method.
(Pdb) dir(request)
['COOKIES', 'FILES', 'GET', 'META', 'POST', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_user', '_cors_enabled', '_current_scheme_host', '_encoding', '_files', '_get_full_path', '_get_post', '_get_raw_host', '_get_scheme', '_initialize_handlers', '_load_post_and_files', '_mark_post_parse_error', '_messages', '_post', '_read_started', '_set_content_type_params', '_set_post', '_stream', '_upload_handlers', 'accepted_types', 'accepts', 'body', 'build_absolute_uri', 'close', 'content_params', 'content_type', 'csrf_processing_done', 'encoding', 'environ', 'get_full_path', 'get_full_path_info', 'get_host', 'get_port', 'get_raw_uri', 'get_signed_cookie', 'headers', 'id', 'is_ajax', 'is_secure', 'method', 'parse_file_upload', 'path', 'path_info', 'prometheus_after_middleware_event', 'prometheus_before_middleware_event', 'read', 'readline', 'readlines', 'resolver_match', 'scheme', 'session', 'upload_handlers', 'user']
Notice the user attribute. Let's see what that is:
Great! We now know how to get the authenticated user by calling request.user. We will take advantage of that in the next step. Explore some more if you like. When you are ready to exit the debugger, type c (continue) and hit the Enter or Return key.
(Pdb) c
[24/Apr/2022 20:44:27] "GET /plugins/maintenance_notices/maintenancenotice/add/ HTTP/1.1" 200 251207
Step 4-9 - Assign the User to the Maintenance Notice¶
Let's make use of our new found knowledge.
Edit the views.py file. Remove the line import pdb; pdb.set_trace() and replace it with obj.created_by = request.user. The obj in this case is an instance of our MaintenanceNotice model, so we can assign the created_by attribute to the request.user object.
class MaintenanceNoticeCreateView(MaintenanceNoticeEditView):
"""Create view."""
def alter_obj(self, obj, request, *args, **kwargs):
"""Insert user into object."""
obj.created_by = request.user
return obj
Save the file. This should cause the nautobot-server to reload. Use the web browser to add another Maintenance Notice and validate our work. This time you should see your username in the created_by field.

Step 4-10 - Commit Your Changes¶
Remember to commit your changes to git:
This lab is now complete. Check your work against the solution guide before proceeding with the next lab.