Skip to content

nautobot.apps.views

Utilities for apps to implement UI views.

nautobot.apps.views.NautobotUIViewSet

Bases: mixins.ObjectDetailViewMixin, mixins.ObjectListViewMixin, mixins.ObjectEditViewMixin, mixins.ObjectDestroyViewMixin, mixins.ObjectBulkDestroyViewMixin, mixins.ObjectBulkCreateViewMixin, mixins.ObjectBulkUpdateViewMixin, mixins.ObjectChangeLogViewMixin, mixins.ObjectNotesViewMixin

Nautobot BaseViewSet that is intended for UI use only. It provides default Nautobot functionalities such as create(), bulk_create(), update(), partial_update(), bulk_update(), destroy(), bulk_destroy(), retrieve() notes(), changelog() and list() actions.

Source code in nautobot/core/views/viewsets.py
class NautobotUIViewSet(
    mixins.ObjectDetailViewMixin,
    mixins.ObjectListViewMixin,
    mixins.ObjectEditViewMixin,
    mixins.ObjectDestroyViewMixin,
    mixins.ObjectBulkDestroyViewMixin,
    mixins.ObjectBulkCreateViewMixin,
    mixins.ObjectBulkUpdateViewMixin,
    mixins.ObjectChangeLogViewMixin,
    mixins.ObjectNotesViewMixin,
):
    """
    Nautobot BaseViewSet that is intended for UI use only. It provides default Nautobot functionalities such as
    `create()`, `bulk_create()`, `update()`, `partial_update()`, `bulk_update()`, `destroy()`, `bulk_destroy()`, `retrieve()`
    `notes()`, `changelog()` and `list()` actions.
    """

nautobot.apps.views.ObjectBulkCreateViewMixin

Bases: NautobotViewSetMixin, BulkCreateModelMixin

UI mixin to bulk create model instances.

Source code in nautobot/core/views/mixins.py
class ObjectBulkCreateViewMixin(NautobotViewSetMixin, BulkCreateModelMixin):
    """
    UI mixin to bulk create model instances.
    """

    bulk_create_active_tab = "csv-data"
    bulk_create_form_class = None
    bulk_create_widget_attrs = {}

    def _process_bulk_create_form(self, form):
        # Iterate through CSV data and bind each row to a new model form instance.
        new_objs = []
        request = self.request
        queryset = self.get_queryset()
        with transaction.atomic():
            if request.FILES:
                field_name = "csv_file"
                # Set the bulk_create_active_tab to "csv-file"
                # In case the form validation fails, the user will be redirected
                # to the tab with errors rendered on the form.
                self.bulk_create_active_tab = "csv-file"
            else:
                field_name = "csv_data"
            headers, records = form.cleaned_data[field_name]
            for row, data in enumerate(records, start=1):
                obj_form = self.bulk_create_form_class(data, headers=headers)
                restrict_form_fields(obj_form, request.user)

                if obj_form.is_valid():
                    obj = self.form_save(obj_form)
                    new_objs.append(obj)
                else:
                    for field, err in obj_form.errors.items():
                        form.add_error(field_name, f"Row {row} {field}: {err[0]}")
                    raise ValidationError("")

            # Enforce object-level permissions
            if queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
                raise ObjectDoesNotExist

        # Compile a table containing the imported objects
        table_class = self.get_table_class()
        obj_table = table_class(new_objs)
        if new_objs:
            msg = f"Imported {len(new_objs)} {new_objs[0]._meta.verbose_name_plural}"
            self.logger.info(msg)
            messages.success(request, msg)
        return obj_table

    def bulk_create(self, request, *args, **kwargs):
        context = {}
        if request.method == "POST":
            return self.perform_bulk_create(request)
        return Response(context)

    def perform_bulk_create(self, request):
        form_class = self.get_form_class()
        form = form_class(request.POST, request.FILES)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

nautobot.apps.views.ObjectBulkDestroyViewMixin

Bases: NautobotViewSetMixin, BulkDestroyModelMixin

UI mixin to bulk destroy model instances.

Source code in nautobot/core/views/mixins.py
class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin):
    """
    UI mixin to bulk destroy model instances.
    """

    bulk_destroy_form_class = None
    filterset_class = None

    def _process_bulk_destroy_form(self, form):
        request = self.request
        pk_list = self.pk_list
        queryset = self.get_queryset()
        model = queryset.model
        # Delete objects
        queryset = queryset.filter(pk__in=pk_list)

        try:
            with transaction.atomic():
                deleted_count = queryset.delete()[1][model._meta.label]
                msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
                self.logger.info(msg)
                self.success_url = self.get_return_url(request)
                messages.success(request, msg)
        except ProtectedError as e:
            self.logger.info("Caught ProtectedError while attempting to delete objects")
            handle_protectederror(queryset, request, e)
            self.success_url = self.get_return_url(request)

    def bulk_destroy(self, request, *args, **kwargs):
        """
        Call perform_bulk_destroy().
        The function exist to keep the DRF's get/post pattern of {action}/perform_{action}, we will need it when we transition from using forms to serializers in the UI.
        User should override this function to handle any actions as needed before bulk destroy.
        """
        return self.perform_bulk_destroy(request, **kwargs)

    def perform_bulk_destroy(self, request, **kwargs):
        """
        request.POST "_delete": Function to render the user selection of objects in a table form/BulkDestroyConfirmationForm via Response that is passed to NautobotHTMLRenderer.
        request.POST "_confirm": Function to validate the table form/BulkDestroyConfirmationForm and to perform the action of bulk destroy. Render the form with errors if exceptions are raised.
        """
        queryset = self.get_queryset()
        model = queryset.model
        # Are we deleting *all* objects in the queryset or just a selected subset?
        if request.POST.get("_all"):
            if self.filterset_class is not None:
                self.pk_list = [obj.pk for obj in self.filterset_class(request.POST, model.objects.only("pk")).qs]
            else:
                self.pk_list = model.objects.values_list("pk", flat=True)
        else:
            self.pk_list = request.POST.getlist("pk")
        form_class = self.get_form_class(**kwargs)
        data = {}
        if "_confirm" in request.POST:
            form = form_class(request.POST)
            if form.is_valid():
                return self.form_valid(form)
            else:
                return self.form_invalid(form)
        table_class = self.get_table_class()
        table = table_class(queryset.filter(pk__in=self.pk_list), orderable=False)
        if not table.rows:
            messages.warning(
                request,
                f"No {queryset.model._meta.verbose_name_plural} were selected for deletion.",
            )
            return redirect(self.get_return_url(request))

        data.update({"table": table})
        return Response(data)

bulk_destroy(request, args, kwargs)

Call perform_bulk_destroy(). The function exist to keep the DRF's get/post pattern of {action}/perform_{action}, we will need it when we transition from using forms to serializers in the UI. User should override this function to handle any actions as needed before bulk destroy.

Source code in nautobot/core/views/mixins.py
def bulk_destroy(self, request, *args, **kwargs):
    """
    Call perform_bulk_destroy().
    The function exist to keep the DRF's get/post pattern of {action}/perform_{action}, we will need it when we transition from using forms to serializers in the UI.
    User should override this function to handle any actions as needed before bulk destroy.
    """
    return self.perform_bulk_destroy(request, **kwargs)

perform_bulk_destroy(request, kwargs)

request.POST "_delete": Function to render the user selection of objects in a table form/BulkDestroyConfirmationForm via Response that is passed to NautobotHTMLRenderer. request.POST "_confirm": Function to validate the table form/BulkDestroyConfirmationForm and to perform the action of bulk destroy. Render the form with errors if exceptions are raised.

Source code in nautobot/core/views/mixins.py
def perform_bulk_destroy(self, request, **kwargs):
    """
    request.POST "_delete": Function to render the user selection of objects in a table form/BulkDestroyConfirmationForm via Response that is passed to NautobotHTMLRenderer.
    request.POST "_confirm": Function to validate the table form/BulkDestroyConfirmationForm and to perform the action of bulk destroy. Render the form with errors if exceptions are raised.
    """
    queryset = self.get_queryset()
    model = queryset.model
    # Are we deleting *all* objects in the queryset or just a selected subset?
    if request.POST.get("_all"):
        if self.filterset_class is not None:
            self.pk_list = [obj.pk for obj in self.filterset_class(request.POST, model.objects.only("pk")).qs]
        else:
            self.pk_list = model.objects.values_list("pk", flat=True)
    else:
        self.pk_list = request.POST.getlist("pk")
    form_class = self.get_form_class(**kwargs)
    data = {}
    if "_confirm" in request.POST:
        form = form_class(request.POST)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)
    table_class = self.get_table_class()
    table = table_class(queryset.filter(pk__in=self.pk_list), orderable=False)
    if not table.rows:
        messages.warning(
            request,
            f"No {queryset.model._meta.verbose_name_plural} were selected for deletion.",
        )
        return redirect(self.get_return_url(request))

    data.update({"table": table})
    return Response(data)

nautobot.apps.views.ObjectBulkUpdateViewMixin

Bases: NautobotViewSetMixin, BulkUpdateModelMixin

UI mixin to bulk update model instances.

Source code in nautobot/core/views/mixins.py
class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin):
    """
    UI mixin to bulk update model instances.
    """

    filterset_class = None
    bulk_update_form_class = None

    def _process_bulk_update_form(self, form):
        request = self.request
        queryset = self.get_queryset()
        model = queryset.model
        form_custom_fields = getattr(form, "custom_fields", [])
        form_relationships = getattr(form, "relationships", [])
        # Standard fields are those that are intrinsic to self.model in the form
        # Relationships, custom fields, object_note are extrinsic fields
        # PK is used to identify an existing instance, not to modify the object
        standard_fields = [
            field
            for field in form.fields
            if field not in form_custom_fields + form_relationships + ["pk"] + ["object_note"]
        ]
        nullified_fields = request.POST.getlist("_nullify")
        form_cf_to_key = {f"cf_{cf.slug}": cf.name for cf in CustomField.objects.get_for_model(model)}
        with transaction.atomic():
            updated_objects = []
            for obj in queryset.filter(pk__in=form.cleaned_data["pk"]):
                self.obj = obj
                # Update standard fields. If a field is listed in _nullify, delete its value.
                for name in standard_fields:
                    try:
                        model_field = model._meta.get_field(name)
                    except FieldDoesNotExist:
                        # This form field is used to modify a field rather than set its value directly
                        model_field = None
                    # Handle nullification
                    if name in form.nullable_fields and name in nullified_fields:
                        if isinstance(model_field, ManyToManyField):
                            getattr(obj, name).set([])
                        else:
                            setattr(obj, name, None if model_field is not None and model_field.null else "")
                    # ManyToManyFields
                    elif isinstance(model_field, ManyToManyField):
                        if form.cleaned_data[name]:
                            getattr(obj, name).set(form.cleaned_data[name])
                    # Normal fields
                    elif form.cleaned_data[name] not in (None, ""):
                        setattr(obj, name, form.cleaned_data[name])
                # Update custom fields
                for field_name in form_custom_fields:
                    if field_name in form.nullable_fields and field_name in nullified_fields:
                        obj.cf[form_cf_to_key[field_name]] = None
                    elif form.cleaned_data.get(field_name) not in (None, "", []):
                        obj.cf[form_cf_to_key[field_name]] = form.cleaned_data[field_name]

                obj.validated_save()
                updated_objects.append(obj)
                self.logger.debug(f"Saved {obj} (PK: {obj.pk})")

                # Add/remove tags
                if form.cleaned_data.get("add_tags", None):
                    obj.tags.add(*form.cleaned_data["add_tags"])
                if form.cleaned_data.get("remove_tags", None):
                    obj.tags.remove(*form.cleaned_data["remove_tags"])

                if hasattr(form, "save_relationships") and callable(form.save_relationships):
                    # Add/remove relationship associations
                    form.save_relationships(instance=obj, nullified_fields=nullified_fields)

                if hasattr(form, "save_note") and callable(form.save_note):
                    form.save_note(instance=obj, user=request.user)

            # Enforce object-level permissions
            if queryset.filter(pk__in=[obj.pk for obj in updated_objects]).count() != len(updated_objects):
                raise ObjectDoesNotExist
        if updated_objects:
            msg = f"Updated {len(updated_objects)} {model._meta.verbose_name_plural}"
            self.logger.info(msg)
            messages.success(self.request, msg)
        self.success_url = self.get_return_url(request)

    def bulk_update(self, request, *args, **kwargs):
        """
        Call perform_bulk_update().
        The function exist to keep the DRF's get/post pattern of {action}/perform_{action}, we will need it when we transition from using forms to serializers in the UI.
        User should override this function to handle any actions as needed before bulk update.
        """
        return self.perform_bulk_update(request, **kwargs)

    # TODO: this conflicts with BulkUpdateModelMixin.perform_bulk_update(self, objects, update_data, partial)
    def perform_bulk_update(self, request, **kwargs):  # pylint: disable=arguments-differ
        """
        request.POST "_edit": Function to render the user selection of objects in a table form/BulkUpdateForm via Response that is passed to NautobotHTMLRenderer.
        request.POST "_apply": Function to validate the table form/BulkUpdateForm and to perform the action of bulk update. Render the form with errors if exceptions are raised.
        """
        queryset = self.get_queryset()
        model = queryset.model

        # If we are editing *all* objects in the queryset, replace the PK list with all matched objects.
        if request.POST.get("_all"):
            if self.filterset_class is not None:
                self.pk_list = [obj.pk for obj in self.filterset_class(request.POST, model.objects.only("pk")).qs]
            else:
                self.pk_list = model.objects.values_list("pk", flat=True)
        else:
            self.pk_list = request.POST.getlist("pk")
        data = {}
        form_class = self.get_form_class()
        if "_apply" in request.POST:
            self.kwargs = kwargs
            form = form_class(model, request.POST)
            restrict_form_fields(form, request.user)
            if form.is_valid():
                return self.form_valid(form)
            else:
                return self.form_invalid(form)
        table_class = self.get_table_class()
        table = table_class(queryset.filter(pk__in=self.pk_list), orderable=False)
        if not table.rows:
            messages.warning(
                request,
                f"No {queryset.model._meta.verbose_name_plural} were selected for deletion.",
            )
            return redirect(self.get_return_url(request))
        data.update({"table": table})
        return Response(data)

bulk_update(request, args, kwargs)

Call perform_bulk_update(). The function exist to keep the DRF's get/post pattern of {action}/perform_{action}, we will need it when we transition from using forms to serializers in the UI. User should override this function to handle any actions as needed before bulk update.

Source code in nautobot/core/views/mixins.py
def bulk_update(self, request, *args, **kwargs):
    """
    Call perform_bulk_update().
    The function exist to keep the DRF's get/post pattern of {action}/perform_{action}, we will need it when we transition from using forms to serializers in the UI.
    User should override this function to handle any actions as needed before bulk update.
    """
    return self.perform_bulk_update(request, **kwargs)

perform_bulk_update(request, kwargs)

request.POST "_edit": Function to render the user selection of objects in a table form/BulkUpdateForm via Response that is passed to NautobotHTMLRenderer. request.POST "_apply": Function to validate the table form/BulkUpdateForm and to perform the action of bulk update. Render the form with errors if exceptions are raised.

Source code in nautobot/core/views/mixins.py
def perform_bulk_update(self, request, **kwargs):  # pylint: disable=arguments-differ
    """
    request.POST "_edit": Function to render the user selection of objects in a table form/BulkUpdateForm via Response that is passed to NautobotHTMLRenderer.
    request.POST "_apply": Function to validate the table form/BulkUpdateForm and to perform the action of bulk update. Render the form with errors if exceptions are raised.
    """
    queryset = self.get_queryset()
    model = queryset.model

    # If we are editing *all* objects in the queryset, replace the PK list with all matched objects.
    if request.POST.get("_all"):
        if self.filterset_class is not None:
            self.pk_list = [obj.pk for obj in self.filterset_class(request.POST, model.objects.only("pk")).qs]
        else:
            self.pk_list = model.objects.values_list("pk", flat=True)
    else:
        self.pk_list = request.POST.getlist("pk")
    data = {}
    form_class = self.get_form_class()
    if "_apply" in request.POST:
        self.kwargs = kwargs
        form = form_class(model, request.POST)
        restrict_form_fields(form, request.user)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)
    table_class = self.get_table_class()
    table = table_class(queryset.filter(pk__in=self.pk_list), orderable=False)
    if not table.rows:
        messages.warning(
            request,
            f"No {queryset.model._meta.verbose_name_plural} were selected for deletion.",
        )
        return redirect(self.get_return_url(request))
    data.update({"table": table})
    return Response(data)

nautobot.apps.views.ObjectChangeLogViewMixin

Bases: NautobotViewSetMixin

UI mixin to list a model's changelog queryset

Source code in nautobot/core/views/mixins.py
class ObjectChangeLogViewMixin(NautobotViewSetMixin):
    """
    UI mixin to list a model's changelog queryset
    """

    base_template = None

    def changelog(self, request, *args, **kwargs):
        data = {
            "base_template": self.base_template,
        }
        return Response(data)

nautobot.apps.views.ObjectDestroyViewMixin

Bases: NautobotViewSetMixin, mixins.DestroyModelMixin

UI mixin to destroy a model instance.

Source code in nautobot/core/views/mixins.py
class ObjectDestroyViewMixin(NautobotViewSetMixin, mixins.DestroyModelMixin):
    """
    UI mixin to destroy a model instance.
    """

    destroy_form_class = ConfirmationForm

    def _process_destroy_form(self, form):
        request = self.request
        obj = self.obj
        queryset = self.get_queryset()
        try:
            with transaction.atomic():
                obj.delete()
                msg = f"Deleted {queryset.model._meta.verbose_name} {obj}"
                self.logger.info(msg)
                messages.success(request, msg)
                self.success_url = self.get_return_url(request, obj)
        except ProtectedError as e:
            self.logger.info("Caught ProtectedError while attempting to delete object")
            handle_protectederror([obj], request, e)
            self.success_url = obj.get_absolute_url()

    def destroy(self, request, *args, **kwargs):
        """
        request.GET: render the ObjectDeleteConfirmationForm which is passed to NautobotHTMLRenderer as Response.
        request.POST: call perform_destroy() which validates the form and perform the action of delete.
        Override to add more variables to Response
        """
        context = {}
        if request.method == "POST":
            return self.perform_destroy(request, **kwargs)
        return Response(context)

    def perform_destroy(self, request, **kwargs):
        """
        Function to validate the ObjectDeleteConfirmationForm and to delete the object.
        """
        self.obj = self.get_object()
        form_class = self.get_form_class()
        form = form_class(request.POST)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

destroy(request, args, kwargs)

request.GET: render the ObjectDeleteConfirmationForm which is passed to NautobotHTMLRenderer as Response. request.POST: call perform_destroy() which validates the form and perform the action of delete. Override to add more variables to Response

Source code in nautobot/core/views/mixins.py
def destroy(self, request, *args, **kwargs):
    """
    request.GET: render the ObjectDeleteConfirmationForm which is passed to NautobotHTMLRenderer as Response.
    request.POST: call perform_destroy() which validates the form and perform the action of delete.
    Override to add more variables to Response
    """
    context = {}
    if request.method == "POST":
        return self.perform_destroy(request, **kwargs)
    return Response(context)

perform_destroy(request, kwargs)

Function to validate the ObjectDeleteConfirmationForm and to delete the object.

Source code in nautobot/core/views/mixins.py
def perform_destroy(self, request, **kwargs):
    """
    Function to validate the ObjectDeleteConfirmationForm and to delete the object.
    """
    self.obj = self.get_object()
    form_class = self.get_form_class()
    form = form_class(request.POST)
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

nautobot.apps.views.ObjectDetailViewMixin

Bases: NautobotViewSetMixin, mixins.RetrieveModelMixin

UI mixin to retrieve a model instance.

Source code in nautobot/core/views/mixins.py
class ObjectDetailViewMixin(NautobotViewSetMixin, mixins.RetrieveModelMixin):
    """
    UI mixin to retrieve a model instance.
    """

nautobot.apps.views.ObjectEditViewMixin

Bases: NautobotViewSetMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin

UI mixin to create or update a model instance.

Source code in nautobot/core/views/mixins.py
class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin):
    """
    UI mixin to create or update a model instance.
    """

    def _process_create_or_update_form(self, form):
        """
        Helper method to create or update an object after the form is validated successfully.
        """
        request = self.request
        queryset = self.get_queryset()
        with transaction.atomic():
            object_created = not form.instance.present_in_database
            obj = self.form_save(form)

            # Check that the new object conforms with any assigned object-level permissions
            queryset.get(pk=obj.pk)

            if hasattr(form, "save_note") and callable(form.save_note):
                form.save_note(instance=obj, user=request.user)

            msg = f'{"Created" if object_created else "Modified"} {queryset.model._meta.verbose_name}'
            self.logger.info(f"{msg} {obj} (PK: {obj.pk})")
            if hasattr(obj, "get_absolute_url"):
                msg = f'{msg} <a href="{obj.get_absolute_url()}">{escape(obj)}</a>'
            else:
                msg = f"{msg} { escape(obj)}"
            messages.success(request, mark_safe(msg))
            if "_addanother" in request.POST:
                # If the object has clone_fields, pre-populate a new instance of the form
                if hasattr(obj, "clone_fields"):
                    url = f"{request.path}?{prepare_cloned_fields(obj)}"
                    self.success_url = url
                self.success_url = request.get_full_path()
            else:
                return_url = form.cleaned_data.get("return_url")
                if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
                    self.success_url = return_url
                else:
                    self.success_url = self.get_return_url(request, obj)

    def create(self, request, *args, **kwargs):
        """
        request.GET: render the ObjectForm which is passed to NautobotHTMLRenderer as Response.
        request.POST: call perform_create() which validates the form and perform the action of create.
        Override to add more variables to Response.
        """
        context = {}
        if request.method == "POST":
            return self.perform_create(request, *args, **kwargs)
        return Response(context)

    # TODO: this conflicts with DRF's CreateModelMixin.perform_create(self, serializer) API
    def perform_create(self, request, *args, **kwargs):  # pylint: disable=arguments-differ
        """
        Function to validate the ObjectForm and to create a new object.
        """
        self.obj = self.get_object()
        form_class = self.get_form_class()
        form = form_class(data=request.POST, files=request.FILES, instance=self.obj)
        restrict_form_fields(form, request.user)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def update(self, request, *args, **kwargs):
        """
        request.GET: render the ObjectEditForm which is passed to NautobotHTMLRenderer as Response.
        request.POST: call perform_update() which validates the form and perform the action of update/partial_update of an existing object.
        Override to add more variables to Response.
        """
        context = {}
        if request.method == "POST":
            return self.perform_update(request, *args, **kwargs)
        return Response(context)

    # TODO: this conflicts with DRF's UpdateModelMixin.perform_update(self, serializer) API
    def perform_update(self, request, *args, **kwargs):  # pylint: disable=arguments-differ
        """
        Function to validate the ObjectEditForm and to update/partial_update an existing object.
        """
        self.obj = self.get_object()
        form_class = self.get_form_class()
        form = form_class(data=request.POST, files=request.FILES, instance=self.obj)
        restrict_form_fields(form, request.user)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

create(request, args, kwargs)

request.GET: render the ObjectForm which is passed to NautobotHTMLRenderer as Response. request.POST: call perform_create() which validates the form and perform the action of create. Override to add more variables to Response.

Source code in nautobot/core/views/mixins.py
def create(self, request, *args, **kwargs):
    """
    request.GET: render the ObjectForm which is passed to NautobotHTMLRenderer as Response.
    request.POST: call perform_create() which validates the form and perform the action of create.
    Override to add more variables to Response.
    """
    context = {}
    if request.method == "POST":
        return self.perform_create(request, *args, **kwargs)
    return Response(context)

perform_create(request, args, kwargs)

Function to validate the ObjectForm and to create a new object.

Source code in nautobot/core/views/mixins.py
def perform_create(self, request, *args, **kwargs):  # pylint: disable=arguments-differ
    """
    Function to validate the ObjectForm and to create a new object.
    """
    self.obj = self.get_object()
    form_class = self.get_form_class()
    form = form_class(data=request.POST, files=request.FILES, instance=self.obj)
    restrict_form_fields(form, request.user)
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

perform_update(request, args, kwargs)

Function to validate the ObjectEditForm and to update/partial_update an existing object.

Source code in nautobot/core/views/mixins.py
def perform_update(self, request, *args, **kwargs):  # pylint: disable=arguments-differ
    """
    Function to validate the ObjectEditForm and to update/partial_update an existing object.
    """
    self.obj = self.get_object()
    form_class = self.get_form_class()
    form = form_class(data=request.POST, files=request.FILES, instance=self.obj)
    restrict_form_fields(form, request.user)
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

update(request, args, kwargs)

request.GET: render the ObjectEditForm which is passed to NautobotHTMLRenderer as Response. request.POST: call perform_update() which validates the form and perform the action of update/partial_update of an existing object. Override to add more variables to Response.

Source code in nautobot/core/views/mixins.py
def update(self, request, *args, **kwargs):
    """
    request.GET: render the ObjectEditForm which is passed to NautobotHTMLRenderer as Response.
    request.POST: call perform_update() which validates the form and perform the action of update/partial_update of an existing object.
    Override to add more variables to Response.
    """
    context = {}
    if request.method == "POST":
        return self.perform_update(request, *args, **kwargs)
    return Response(context)

nautobot.apps.views.ObjectListViewMixin

Bases: NautobotViewSetMixin, mixins.ListModelMixin

UI mixin to list a model queryset

Source code in nautobot/core/views/mixins.py
class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
    """
    UI mixin to list a model queryset
    """

    action_buttons = ("add", "import", "export")
    filterset_class = None
    filterset_form_class = None
    non_filter_params = (
        "export",  # trigger for CSV/export-template/YAML export
        "page",  # used by django-tables2.RequestConfig
        "per_page",  # used by get_paginate_count
        "sort",  # table sorting
    )

    def check_for_export(self, request, model, content_type):
        # Check for export template rendering
        queryset = self.get_queryset()
        if request.GET.get("export"):
            et = get_object_or_404(
                ExportTemplate,
                content_type=content_type,
                name=request.GET.get("export"),
            )
            try:
                return et.render_to_response(queryset)
            except Exception as e:
                messages.error(
                    request,
                    f"There was an error rendering the selected export template ({et.name}): {e}",
                )

        # Check for YAML export support
        elif "export" in request.GET and hasattr(model, "to_yaml"):
            response = HttpResponse(self.queryset_to_yaml(), content_type="text/yaml")
            filename = f"nautobot_{queryset.model._meta.verbose_name_plural}.yaml"
            response["Content-Disposition"] = f'attachment; filename="{filename}"'
            return response

        # Fall back to built-in CSV formatting if export requested but no template specified
        elif "export" in request.GET and hasattr(model, "to_csv"):
            response = HttpResponse(self.queryset_to_csv(), content_type="text/csv")
            filename = f"nautobot_{queryset.model._meta.verbose_name_plural}.csv"
            response["Content-Disposition"] = f'attachment; filename="{filename}"'
            return response

        return None

    def queryset_to_yaml(self):
        """
        Export the queryset of objects as concatenated YAML documents.
        """
        queryset = self.get_queryset()
        yaml_data = [obj.to_yaml() for obj in queryset]

        return "---\n".join(yaml_data)

    def queryset_to_csv(self):
        """
        Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
        """
        queryset = self.get_queryset()
        csv_data = []
        custom_fields = []
        # Start with the column headers
        headers = queryset.model.csv_headers.copy()

        # Add custom field headers, if any
        if hasattr(queryset.model, "_custom_field_data"):
            for custom_field in CustomField.objects.get_for_model(queryset.model):
                headers.append("cf_" + custom_field.slug)
                custom_fields.append(custom_field.name)

        csv_data.append(",".join(headers))

        # Iterate through the queryset appending each object
        for obj in queryset:
            data = obj.to_csv()

            for custom_field in custom_fields:
                data += (obj.cf.get(custom_field, ""),)

            csv_data.append(csv_format(data))

        return "\n".join(csv_data)

    def list(self, request, *args, **kwargs):
        """
        List the model instances.
        """
        context = {}
        if "export" in request.GET:
            queryset = self.get_queryset()
            model = queryset.model
            content_type = ContentType.objects.get_for_model(model)
            response = self.check_for_export(request, model, content_type)
            if response is not None:
                return response
        return Response(context)

list(request, args, kwargs)

List the model instances.

Source code in nautobot/core/views/mixins.py
def list(self, request, *args, **kwargs):
    """
    List the model instances.
    """
    context = {}
    if "export" in request.GET:
        queryset = self.get_queryset()
        model = queryset.model
        content_type = ContentType.objects.get_for_model(model)
        response = self.check_for_export(request, model, content_type)
        if response is not None:
            return response
    return Response(context)

queryset_to_csv()

Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.

Source code in nautobot/core/views/mixins.py
def queryset_to_csv(self):
    """
    Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
    """
    queryset = self.get_queryset()
    csv_data = []
    custom_fields = []
    # Start with the column headers
    headers = queryset.model.csv_headers.copy()

    # Add custom field headers, if any
    if hasattr(queryset.model, "_custom_field_data"):
        for custom_field in CustomField.objects.get_for_model(queryset.model):
            headers.append("cf_" + custom_field.slug)
            custom_fields.append(custom_field.name)

    csv_data.append(",".join(headers))

    # Iterate through the queryset appending each object
    for obj in queryset:
        data = obj.to_csv()

        for custom_field in custom_fields:
            data += (obj.cf.get(custom_field, ""),)

        csv_data.append(csv_format(data))

    return "\n".join(csv_data)

queryset_to_yaml()

Export the queryset of objects as concatenated YAML documents.

Source code in nautobot/core/views/mixins.py
def queryset_to_yaml(self):
    """
    Export the queryset of objects as concatenated YAML documents.
    """
    queryset = self.get_queryset()
    yaml_data = [obj.to_yaml() for obj in queryset]

    return "---\n".join(yaml_data)

nautobot.apps.views.ObjectNotesViewMixin

Bases: NautobotViewSetMixin

UI Mixin for an Object's Notes.

Source code in nautobot/core/views/mixins.py
class ObjectNotesViewMixin(NautobotViewSetMixin):
    """
    UI Mixin for an Object's Notes.
    """

    base_template = None

    def notes(self, request, *args, **kwargs):
        data = {
            "base_template": self.base_template,
        }
        return Response(data)

nautobot.apps.views.ObjectView

Bases: ObjectPermissionRequiredMixin, View

Retrieve a single object for display.

queryset: The base queryset for retrieving the object template_name: Name of the template to use

Source code in nautobot/core/views/generic.py
class ObjectView(ObjectPermissionRequiredMixin, View):
    """
    Retrieve a single object for display.

    queryset: The base queryset for retrieving the object
    template_name: Name of the template to use
    """

    queryset = None
    template_name = None

    def get_required_permission(self):
        return get_permission_for_model(self.queryset.model, "view")

    def get_template_name(self):
        """
        Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.
        """
        if self.template_name is not None:
            return self.template_name
        model_opts = self.queryset.model._meta
        return f"{model_opts.app_label}/{model_opts.model_name}.html"

    def get_extra_context(self, request, instance):
        """
        Return any additional context data for the template.

        Args:
            request (Request): The current request
            instance (Model): The object being viewed

        Returns:
            (dict): Additional context data
        """
        return {
            "active_tab": request.GET.get("tab", "main"),
        }

    # 2.0 TODO: Remove this method in 2.0. Can be retrieved from instance itself now
    # instance.get_changelog_url()
    # Only available on models that support changelogs
    def get_changelog_url(self, instance):
        """Return the changelog URL for a given instance."""
        meta = self.queryset.model._meta

        # Don't try to generate a changelog_url for an ObjectChange.
        if meta.model_name == "objectchange":
            return None

        route = get_route_for_model(instance, "changelog")

        # Iterate the pk-like fields and try to get a URL, or return None.
        fields = ["pk", "slug"]
        for field in fields:
            if not hasattr(instance, field):
                continue

            try:
                return reverse(route, kwargs={field: getattr(instance, field)})
            except NoReverseMatch:
                continue

        # This object likely doesn't have a changelog route defined.
        return None

    def get(self, request, *args, **kwargs):
        """
        Generic GET handler for accessing an object by PK or slug
        """
        instance = get_object_or_404(self.queryset, **kwargs)

        changelog_url = None

        if isinstance(instance, ChangeLoggedModel):
            changelog_url = instance.get_changelog_url()

        return render(
            request,
            self.get_template_name(),
            {
                "object": instance,
                "verbose_name": self.queryset.model._meta.verbose_name,
                "verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
                "changelog_url": changelog_url,  # 2.0 TODO: Remove in 2.0. This information can be retrieved from the object itself now.
                **self.get_extra_context(request, instance),
            },
        )

get(request, args, kwargs)

Generic GET handler for accessing an object by PK or slug

Source code in nautobot/core/views/generic.py
def get(self, request, *args, **kwargs):
    """
    Generic GET handler for accessing an object by PK or slug
    """
    instance = get_object_or_404(self.queryset, **kwargs)

    changelog_url = None

    if isinstance(instance, ChangeLoggedModel):
        changelog_url = instance.get_changelog_url()

    return render(
        request,
        self.get_template_name(),
        {
            "object": instance,
            "verbose_name": self.queryset.model._meta.verbose_name,
            "verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
            "changelog_url": changelog_url,  # 2.0 TODO: Remove in 2.0. This information can be retrieved from the object itself now.
            **self.get_extra_context(request, instance),
        },
    )

get_changelog_url(instance)

Return the changelog URL for a given instance.

Source code in nautobot/core/views/generic.py
def get_changelog_url(self, instance):
    """Return the changelog URL for a given instance."""
    meta = self.queryset.model._meta

    # Don't try to generate a changelog_url for an ObjectChange.
    if meta.model_name == "objectchange":
        return None

    route = get_route_for_model(instance, "changelog")

    # Iterate the pk-like fields and try to get a URL, or return None.
    fields = ["pk", "slug"]
    for field in fields:
        if not hasattr(instance, field):
            continue

        try:
            return reverse(route, kwargs={field: getattr(instance, field)})
        except NoReverseMatch:
            continue

    # This object likely doesn't have a changelog route defined.
    return None

get_extra_context(request, instance)

Return any additional context data for the template.

Parameters:

Name Type Description Default
request Request

The current request

required
instance Model

The object being viewed

required

Returns:

Type Description
dict

Additional context data

Source code in nautobot/core/views/generic.py
def get_extra_context(self, request, instance):
    """
    Return any additional context data for the template.

    Args:
        request (Request): The current request
        instance (Model): The object being viewed

    Returns:
        (dict): Additional context data
    """
    return {
        "active_tab": request.GET.get("tab", "main"),
    }

get_template_name()

Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.

Source code in nautobot/core/views/generic.py
def get_template_name(self):
    """
    Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.
    """
    if self.template_name is not None:
        return self.template_name
    model_opts = self.queryset.model._meta
    return f"{model_opts.app_label}/{model_opts.model_name}.html"