From ca4589dbb9ab121fbb05eb641b8d0b3053734205 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Wed, 22 Apr 2026 14:33:13 +0000 Subject: [PATCH 1/2] Add Add-button on Typed tabs with reverse-reference prefill (closes #9) Each typed tab now exposes an "Add " button that links to the native customobject_add view with the back-reference field pre-populated to the parent object's PK and return_url set to the current tab path. After saving, the user lands back on the tab with filters preserved. When a Custom Object Type has multiple back-reference fields to the same parent model (e.g. primary_device + backup_device both -> dcim.device), the button becomes a Bootstrap split-dropdown listing each field. The button is hidden for users without add_customobject permission. Implementation: - New module-level _build_add_links() helper computes URLs from (slug, instance_pk, field_infos, return_url). Pure function, fully unit-tested. - field_infos tuples extended from (name, type) to (name, type, label) so the dropdown can show human-readable field labels. Star-unpacking in _count_for_type and the queryset filter loop preserves backward compatibility with 2-tuple shapes used by existing tests. - Permission gate uses utilities.permissions.get_permission_for_model, matching the pattern used by netbox_custom_objects.tables.CustomObjectActionsColumn. - field_infos is sorted by field name in register_typed_tabs so the dropdown order is deterministic. Tab placement: top-right of tab content, scoped entirely to typed/tab.html. Tabs with hide_if_empty=True remain hidden until the first object is created via the native menu - the button surfaces once the tab is visible. Bumps version to 2.2.0 (minor, new feature). Test suite extended from 53 to 61 tests covering reverse failure, single/multi field, deduplication, label fallback, 2-tuple back-compat, and return_url URL-encoding. --- CHANGELOG.md | 15 + .../netbox_custom_objects_tab/typed/tab.html | 28 ++ netbox_custom_objects_tab/views/typed.py | 65 ++- pyproject.toml | 2 +- tests/conftest.py | 65 +-- tests/settings.py | 12 +- tests/test_views_typed_smoke.py | 422 ++++++++++++++++-- 7 files changed, 529 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c8751..e6b2333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.0] - 2026-04-22 + +### Added + +- **Add button on Typed tabs** ([#9](https://github.com/CESNET/netbox-custom-objects-tab/issues/9)) — + each Typed tab now shows an "Add *Type*" button (top-right) that opens the native + `customobject_add` view with the reverse-reference field pre-filled to the parent + object's PK and `return_url` set back to the tab. After saving, the user lands back + on the same tab, with any active filters preserved. When a Custom Object Type has + multiple fields referencing the same parent model (e.g. `primary_device` and + `backup_device` both → Device), the button becomes a split-dropdown listing each + field. The button is hidden for users without `add_customobject` permission. + Tabs with `hide_if_empty=True` are still hidden until the first object is created + via the native menu — subsequent additions can use the new button. + ## [2.1.0] - 2026-03-16 ### Added diff --git a/netbox_custom_objects_tab/templates/netbox_custom_objects_tab/typed/tab.html b/netbox_custom_objects_tab/templates/netbox_custom_objects_tab/typed/tab.html index 3ad9097..ad313c3 100644 --- a/netbox_custom_objects_tab/templates/netbox_custom_objects_tab/typed/tab.html +++ b/netbox_custom_objects_tab/templates/netbox_custom_objects_tab/typed/tab.html @@ -5,6 +5,34 @@ {% load i18n %} {% block content %} {% if table %} + {# Add button (single) or split-dropdown (multiple back-reference fields) #} + {% if can_add and add_links %} +
+ {% if add_links|length == 1 %} + + + {% blocktrans with label=add_label %}Add {{ label }}{% endblocktrans %} + + {% else %} +
+ + +
+ {% endif %} +
+ {% endif %}
{# Results / Filters inner tabs #}