mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-06 07:16:25 -06:00
Merge branch 'main' into feature
CI / build (20.x, 3.12) (push) Has been cancelled
CI / build (20.x, 3.13) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
CI / build (20.x, 3.13) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
This commit is contained in:
@@ -82,7 +82,7 @@ def get_view_name(view):
|
||||
Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name()`.
|
||||
This function is provided to DRF as its VIEW_NAME_FUNCTION.
|
||||
"""
|
||||
if hasattr(view, 'queryset'):
|
||||
if hasattr(view, 'queryset') and view.queryset is not None:
|
||||
# Derive the model name from the queryset.
|
||||
name = title(view.queryset.model._meta.verbose_name)
|
||||
if suffix := getattr(view, 'suffix', None):
|
||||
|
||||
@@ -53,6 +53,14 @@ class SlugField(forms.SlugField):
|
||||
|
||||
self.widget.attrs['slug-source'] = slug_source
|
||||
|
||||
def get_bound_field(self, form, field_name):
|
||||
if prefix := form.prefix:
|
||||
slug_source = self.widget.attrs.get('slug-source')
|
||||
if slug_source and not slug_source.startswith(f'{prefix}-'):
|
||||
self.widget.attrs['slug-source'] = f"{prefix}-{slug_source}"
|
||||
|
||||
return super().get_bound_field(form, field_name)
|
||||
|
||||
|
||||
class ColorField(forms.CharField):
|
||||
"""
|
||||
|
||||
@@ -56,6 +56,14 @@ class SlugWidget(forms.TextInput):
|
||||
"""
|
||||
template_name = 'widgets/sluginput.html'
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
local_attrs = {} if attrs is None else attrs.copy()
|
||||
if 'class' in local_attrs:
|
||||
local_attrs['class'] = f"{local_attrs['class']} slug-field"
|
||||
else:
|
||||
local_attrs['class'] = 'slug-field'
|
||||
super().__init__(local_attrs)
|
||||
|
||||
|
||||
class ArrayWidget(forms.Textarea):
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
__all__ = (
|
||||
'htmx_current_url',
|
||||
'htmx_partial',
|
||||
'htmx_maybe_redirect_current_page',
|
||||
)
|
||||
|
||||
|
||||
@@ -9,3 +15,45 @@ def htmx_partial(request):
|
||||
in response to an HTMX request, based on the target element.
|
||||
"""
|
||||
return request.htmx and not request.htmx.boosted
|
||||
|
||||
|
||||
def htmx_current_url(request) -> str:
|
||||
"""
|
||||
Extracts the current URL from the HTMX-specific headers in the given request object.
|
||||
|
||||
This function checks for the `HX-Current-URL` header in the request's headers
|
||||
and `HTTP_HX_CURRENT_URL` in the META data of the request. It preferentially
|
||||
chooses the value present in the `HX-Current-URL` header and falls back to the
|
||||
`HTTP_HX_CURRENT_URL` META data if the former is unavailable. If neither value
|
||||
exists, it returns an empty string.
|
||||
"""
|
||||
return request.headers.get('HX-Current-URL') or request.META.get('HTTP_HX_CURRENT_URL', '') or ''
|
||||
|
||||
|
||||
def htmx_maybe_redirect_current_page(
|
||||
request, url_name: str, *, preserve_query: bool = True, status: int = 200
|
||||
) -> HttpResponse | None:
|
||||
"""
|
||||
Redirects the current page in an HTMX request if conditions are met.
|
||||
|
||||
This function checks whether a request is an HTMX partial request and if the
|
||||
current URL matches the provided target URL. If the conditions are met, it
|
||||
returns an HTTP response signaling a redirect to the provided or updated target
|
||||
URL. Otherwise, it returns None.
|
||||
"""
|
||||
if not htmx_partial(request):
|
||||
return None
|
||||
|
||||
current = urlsplit(htmx_current_url(request))
|
||||
target_path = reverse(url_name) # will raise NoReverseMatch if misconfigured
|
||||
|
||||
if current.path.rstrip('/') != target_path.rstrip('/'):
|
||||
return None
|
||||
|
||||
redirect_to = target_path
|
||||
if preserve_query and current.query:
|
||||
redirect_to = f'{target_path}?{current.query}'
|
||||
|
||||
resp = HttpResponse(status=status)
|
||||
resp['HX-Redirect'] = redirect_to
|
||||
return resp
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{% if field|widget_type == 'slugwidget' %}
|
||||
<div class="input-group">
|
||||
{{ field }}
|
||||
<button id="reslug" type="button" title="{% trans "Regenerate Slug" %}" class="btn">
|
||||
<button type="button" title="{% trans "Regenerate Slug" %}" class="btn reslug">
|
||||
<i class="mdi mdi-reload"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.test import Client, TestCase, override_settings
|
||||
from django.test import Client, TestCase, override_settings, tag
|
||||
from django.urls import reverse
|
||||
from drf_spectacular.drainage import GENERATOR_STATS
|
||||
from rest_framework import status
|
||||
@@ -9,6 +9,7 @@ from extras.choices import CustomFieldTypeChoices
|
||||
from extras.models import CustomField
|
||||
from ipam.models import VLAN
|
||||
from netbox.config import get_config
|
||||
from utilities.api import get_view_name
|
||||
from utilities.testing import APITestCase, disable_warnings
|
||||
|
||||
|
||||
@@ -267,3 +268,19 @@ class APIDocsTestCase(TestCase):
|
||||
with GENERATOR_STATS.silence(): # Suppress schema generator warnings
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class GetViewNameTestCase(TestCase):
|
||||
|
||||
@tag('regression')
|
||||
def test_get_view_name_with_none_queryset(self):
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
class MockViewSet(ReadOnlyModelViewSet):
|
||||
queryset = None
|
||||
|
||||
view = MockViewSet()
|
||||
view.suffix = 'List'
|
||||
|
||||
name = get_view_name(view)
|
||||
self.assertEqual(name, 'Mock List')
|
||||
|
||||
Reference in New Issue
Block a user