diff --git a/.github/workflows/lint-tests.yaml b/.github/workflows/lint-tests.yaml index db24452..08e137a 100644 --- a/.github/workflows/lint-tests.yaml +++ b/.github/workflows/lint-tests.yaml @@ -69,7 +69,7 @@ jobs: with: repository: "netbox-community/netbox" path: netbox - ref: feature + ref: main - name: Install netbox-custom-objects working-directory: netbox-custom-objects run: | diff --git a/netbox_custom_objects/api/views.py b/netbox_custom_objects/api/views.py index 039456f..89a334a 100644 --- a/netbox_custom_objects/api/views.py +++ b/netbox_custom_objects/api/views.py @@ -1,13 +1,19 @@ from django.http import Http404 +from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema_view, extend_schema from rest_framework.routers import APIRootView from rest_framework.viewsets import ModelViewSet +from rest_framework.exceptions import ValidationError from netbox_custom_objects.filtersets import get_filterset_class from netbox_custom_objects.models import CustomObjectType, CustomObjectTypeField +from netbox_custom_objects.utilities import is_in_branch from . import serializers +# Constants +BRANCH_ACTIVE_ERROR_MESSAGE = _("Please switch to the main branch to perform this operation.") + class RootView(APIRootView): def get_view_name(self): @@ -60,6 +66,21 @@ def filterset_class(self): def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) + def create(self, request, *args, **kwargs): + if is_in_branch(): + raise ValidationError(BRANCH_ACTIVE_ERROR_MESSAGE) + return super().create(request, *args, **kwargs) + + def update(self, request, *args, **kwargs): + if is_in_branch(): + raise ValidationError(BRANCH_ACTIVE_ERROR_MESSAGE) + return super().update(request, *args, **kwargs) + + def partial_update(self, request, *args, **kwargs): + if is_in_branch(): + raise ValidationError(BRANCH_ACTIVE_ERROR_MESSAGE) + return super().partial_update(request, *args, **kwargs) + class CustomObjectTypeFieldViewSet(ModelViewSet): queryset = CustomObjectTypeField.objects.all() diff --git a/netbox_custom_objects/templates/netbox_custom_objects/custom_object_bulk_edit.html b/netbox_custom_objects/templates/netbox_custom_objects/custom_object_bulk_edit.html new file mode 100644 index 0000000..2ed1a51 --- /dev/null +++ b/netbox_custom_objects/templates/netbox_custom_objects/custom_object_bulk_edit.html @@ -0,0 +1,103 @@ +{% extends 'generic/bulk_edit.html' %} +{% load form_helpers %} +{% load helpers %} +{% load i18n %} +{% load render_table from django_tables2 %} + + +{% block content %} + + {# Edit form #} +
+ {% if branch_warning %} + {% include 'netbox_custom_objects/inc/branch_warning.html' %} + {% endif %} + +
+
+ {% csrf_token %} + {% if request.POST.return_url %} + + {% endif %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + + {% if form.fieldsets %} + + {# Render grouped fields according to declared fieldsets #} + {% for fieldset in form.fieldsets %} + {% render_fieldset form fieldset %} + {% endfor %} + + {# Render tag add/remove fields #} + {% if form.add_tags and form.remove_tags %} +
+
+

{% trans "Tags" %}

+
+ {% render_field form.add_tags %} + {% render_field form.remove_tags %} +
+ {% endif %} + + {# Render custom fields #} + {% if form.custom_fields %} +
+
+

{% trans "Custom Fields" %}

+
+ {% render_custom_fields form %} +
+ {% endif %} + + {# Render comments #} + {% if form.comments %} +
+
+

{% trans "Comments" %}

+
+ {% render_field form.comments bulk_nullable=True %} +
+ {% endif %} + + {% else %} + + {# Render all fields #} + {% for field in form.visible_fields %} + {% if field.name in form.meta_fields %} + {% elif field.name in form.nullable_fields %} + {% render_field field bulk_nullable=True %} + {% else %} + {% render_field field %} + {% endif %} + {% endfor %} + + {% endif %} + + {# Meta fields #} +
+ {% if form.changelog_message %} + {% render_field form.changelog_message %} + {% endif %} + {% render_field form.background_job %} +
+ +
+ {% trans "Cancel" %} + +
+
+
+
+ + {# Selected objects list #} +
+
+
+ {% render_table table 'inc/table.html' %} +
+
+
+ +{% endblock content %} diff --git a/netbox_custom_objects/templates/netbox_custom_objects/custom_object_bulk_import.html b/netbox_custom_objects/templates/netbox_custom_objects/custom_object_bulk_import.html new file mode 100644 index 0000000..3ae447b --- /dev/null +++ b/netbox_custom_objects/templates/netbox_custom_objects/custom_object_bulk_import.html @@ -0,0 +1,209 @@ +{% extends 'generic/bulk_import.html' %} +{% load form_helpers %} +{% load helpers %} +{% load i18n %} + + +{% block content %} + + {# Data Import Form #} +
+
+ + {% if branch_warning %} + {% include 'netbox_custom_objects/inc/branch_warning.html' %} + {% endif %} + +
+ {% csrf_token %} + + + {# Form fields #} + {% render_field form.data %} + {% render_field form.format %} + {% render_field form.csv_delimiter %} + + {# Meta fields #} +
+ {% if form.changelog_message %} + {% render_field form.changelog_message %} + {% endif %} + {% render_field form.background_job %} +
+ +
+
+ {% if return_url %} + {% trans "Cancel" %} + {% endif %} + +
+
+
+
+
+ + {# File Upload Form #} +
+
+
+ {% csrf_token %} + + + {# Form fields #} + {% render_field form.upload_file %} + {% render_field form.format %} + {% render_field form.csv_delimiter %} + + {# Meta fields #} + {# Background jobs not supported with file uploads #} + {% if form.changelog_message %} +
+ {% render_field form.changelog_message %} +
+ {% endif %} + +
+
+ {% if return_url %} + {% trans "Cancel" %} + {% endif %} + +
+
+
+
+
+ + {# DataFile Form #} +
+
+
+ {% csrf_token %} + + + {# Form fields #} + {% render_field form.data_source %} + {% render_field form.data_file %} + {% render_field form.format %} + {% render_field form.csv_delimiter %} + + {# Meta fields #} +
+ {% if form.changelog_message %} + {% render_field form.changelog_message %} + {% endif %} + {% render_field form.background_job %} +
+ +
+
+ {% if return_url %} + {% trans "Cancel" %} + {% endif %} + +
+
+
+
+
+ + {% if fields %} +
+
+
+

{% trans "Field Options" %}

+ + + + + + + + + + + {% for name, field in fields.items %} + + + + {% if field.to_field_name %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
{% trans "Field" %}{% trans "Required" %}{% trans "Accessor" %}{% trans "Description" %}
+ {{ name }} + + {% if field.required %} + {% checkmark True true="Required" %} + {% else %} + {{ ''|placeholder }} + {% endif %} + {{ field.to_field_name }}{{ ''|placeholder }} + {% if field.help_text %} + {{ field.help_text }} + {% elif field.label %} + {{ field.label }} + {% endif %} + {% if field.STATIC_CHOICES %} + + + {% endif %} + {% if field|widget_type == 'dateinput' %} +
{% trans "Format: YYYY-MM-DD" %} + {% elif field|widget_type == 'checkboxinput' %} +
{% trans "Specify true or false" %} + {% endif %} +
+
+
+
+

+ + {% blocktrans trimmed %} + Required fields must be specified for all objects. + {% endblocktrans %} +

+

+ + {% blocktrans trimmed with example="vrf.rd" %} + Related objects may be referenced by any unique attribute. For example, {{ example }} would identify a VRF by its route distinguisher. + {% endblocktrans %} +

+ {% endif %} + +{% endblock content %} diff --git a/netbox_custom_objects/templates/netbox_custom_objects/customobject_edit.html b/netbox_custom_objects/templates/netbox_custom_objects/customobject_edit.html index db2d138..6cba146 100644 --- a/netbox_custom_objects/templates/netbox_custom_objects/customobject_edit.html +++ b/netbox_custom_objects/templates/netbox_custom_objects/customobject_edit.html @@ -45,4 +45,22 @@

{{ group }}

{% endif %} {% endfor %} -{% endblock form %} \ No newline at end of file +{% endblock form %} + +{% block buttons %} + {% trans "Cancel" %} + {% if object.pk %} + + {% else %} +
+ + +
+ {% endif %} +{% endblock buttons %} \ No newline at end of file diff --git a/netbox_custom_objects/templates/netbox_custom_objects/inc/branch_warning.html b/netbox_custom_objects/templates/netbox_custom_objects/inc/branch_warning.html index 98b9e68..ed7d027 100644 --- a/netbox_custom_objects/templates/netbox_custom_objects/inc/branch_warning.html +++ b/netbox_custom_objects/templates/netbox_custom_objects/inc/branch_warning.html @@ -6,7 +6,7 @@ {% blocktrans trimmed %} - This object has fields that reference objects in other apps and you have a branch active. Care must be taken to not reference an object that only exists in another branch. + Please switch to the main branch to perform this operation. {% endblocktrans %} diff --git a/netbox_custom_objects/views.py b/netbox_custom_objects/views.py index d3ccc50..a25733b 100644 --- a/netbox_custom_objects/views.py +++ b/netbox_custom_objects/views.py @@ -574,20 +574,8 @@ def custom_save(self, commit=True): return form_class def get_extra_context(self, request, obj): - - # Check if we're in a branch and if there are external object pointers - has_external_object_pointers = False - if is_in_branch(): - # Check all fields in the custom object type - for field in self.object.custom_object_type.fields.all(): - if field.type in [CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT]: - # Check if the related object type is not from the current app - if field.related_object_type.app_label != APP_LABEL: - has_external_object_pointers = True - break - return { - 'branch_warning': has_external_object_pointers, + 'branch_warning': is_in_branch(), } @@ -634,6 +622,7 @@ def get_return_url(self, request, obj=None): @register_model_view(CustomObject, "bulk_edit", path="edit", detail=False) class CustomObjectBulkEditView(CustomObjectTableMixin, generic.BulkEditView): + template_name = "netbox_custom_objects/custom_object_bulk_edit.html" queryset = None custom_object_type = None table = None @@ -690,6 +679,11 @@ def get_form(self, queryset): return form + def get_extra_context(self, request): + return { + 'branch_warning': is_in_branch(), + } + @register_model_view(CustomObject, "bulk_delete", path="delete", detail=False) class CustomObjectBulkDeleteView(CustomObjectTableMixin, generic.BulkDeleteView): @@ -706,9 +700,9 @@ def setup(self, request, *args, **kwargs): def get_queryset(self, request): if self.queryset: return self.queryset - custom_object_type = self.kwargs.pop("custom_object_type", None) + self.custom_object_type = self.kwargs.pop("custom_object_type", None) self.custom_object_type = CustomObjectType.objects.get( - slug=custom_object_type + slug=self.custom_object_type ) model = self.custom_object_type.get_model_with_serializer() return model.objects.all() @@ -716,8 +710,10 @@ def get_queryset(self, request): @register_model_view(CustomObject, "bulk_import", path="import", detail=False) class CustomObjectBulkImportView(generic.BulkImportView): + template_name = "netbox_custom_objects/custom_object_bulk_import.html" queryset = None model_form = None + custom_object_type = None def get(self, request, custom_object_type): # Necessary because get() in BulkImportView only takes request and no **kwargs @@ -774,6 +770,11 @@ def get_model_form(self, queryset): return form + def get_extra_context(self, request): + return { + 'branch_warning': is_in_branch(), + } + class CustomObjectJournalView(ConditionalLoginRequiredMixin, View): """