mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 09:16:10 -06:00
Merge branch 'netbox-community:develop' into develop
This commit is contained in:
commit
8dce984f2e
@ -4,6 +4,7 @@
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object
|
||||
* [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text
|
||||
|
||||
---
|
||||
|
@ -7,12 +7,14 @@ class Empty(Lookup):
|
||||
Filter on whether a string is empty.
|
||||
"""
|
||||
lookup_name = 'empty'
|
||||
prepare_rhs = False
|
||||
|
||||
def as_sql(self, qn, connection):
|
||||
lhs, lhs_params = self.process_lhs(qn, connection)
|
||||
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||
params = lhs_params + rhs_params
|
||||
return 'CAST(LENGTH(%s) AS BOOLEAN) != %s' % (lhs, rhs), params
|
||||
def as_sql(self, compiler, connection):
|
||||
sql, params = compiler.compile(self.lhs)
|
||||
if self.rhs:
|
||||
return f"CAST(LENGTH({sql}) AS BOOLEAN) IS NOT TRUE", params
|
||||
else:
|
||||
return f"CAST(LENGTH({sql}) AS BOOLEAN) IS TRUE", params
|
||||
|
||||
|
||||
class NetContainsOrEquals(Lookup):
|
||||
|
@ -328,6 +328,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||
):
|
||||
self.initial['primary_for_parent'] = True
|
||||
|
||||
# Disable object assignment fields if the IP address is designated as primary
|
||||
if self.initial.get('primary_for_parent'):
|
||||
self.fields['interface'].disabled = True
|
||||
self.fields['vminterface'].disabled = True
|
||||
self.fields['fhrpgroup'].disabled = True
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
@ -340,7 +346,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||
selected_objects[1]: "An IP address can only be assigned to a single object."
|
||||
})
|
||||
elif selected_objects:
|
||||
self.instance.assigned_object = self.cleaned_data[selected_objects[0]]
|
||||
assigned_object = self.cleaned_data[selected_objects[0]]
|
||||
if self.cleaned_data['primary_for_parent'] and assigned_object != self.instance.assigned_object:
|
||||
raise ValidationError(
|
||||
"Cannot reassign IP address while it is designated as the primary IP for the parent object"
|
||||
)
|
||||
self.instance.assigned_object = assigned_object
|
||||
else:
|
||||
self.instance.assigned_object = None
|
||||
|
||||
|
@ -177,7 +177,8 @@ class BaseFilterSet(django_filters.FilterSet):
|
||||
# create the new filter with the same type because there is no guarantee the defined type
|
||||
# is the same as the default type for the field
|
||||
resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
|
||||
new_filter = type(existing_filter)(
|
||||
filter_cls = django_filters.BooleanFilter if lookup_expr == 'empty' else type(existing_filter)
|
||||
new_filter = filter_cls(
|
||||
field_name=field_name,
|
||||
lookup_expr=lookup_expr,
|
||||
label=existing_filter.label,
|
||||
@ -224,6 +225,14 @@ class BaseFilterSet(django_filters.FilterSet):
|
||||
|
||||
return filters
|
||||
|
||||
@classmethod
|
||||
def filter_for_lookup(cls, field, lookup_type):
|
||||
|
||||
if lookup_type == 'empty':
|
||||
return django_filters.BooleanFilter, {}
|
||||
|
||||
return super().filter_for_lookup(field, lookup_type)
|
||||
|
||||
|
||||
class ChangeLoggedModelFilterSet(BaseFilterSet):
|
||||
"""
|
||||
|
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
@ -30,6 +30,7 @@
|
||||
"dayjs": "^1.11.5",
|
||||
"flatpickr": "4.6.13",
|
||||
"gridstack": "^7.2.3",
|
||||
"html-entities": "^2.3.3",
|
||||
"htmx.org": "^1.8.0",
|
||||
"just-debounce-it": "^3.1.1",
|
||||
"query-string": "^7.1.1",
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { readableColor } from 'color2k';
|
||||
import debounce from 'just-debounce-it';
|
||||
import { encode } from 'html-entities';
|
||||
import queryString from 'query-string';
|
||||
import SlimSelect from 'slim-select';
|
||||
import { createToast } from '../../bs';
|
||||
@ -446,7 +447,7 @@ export class APISelect {
|
||||
// Build SlimSelect options from all already-selected options.
|
||||
const preSelectedOptions = preSelected.map(option => ({
|
||||
value: option.value,
|
||||
text: option.innerText,
|
||||
text: encode(option.innerText),
|
||||
selected: true,
|
||||
disabled: false,
|
||||
})) as Option[];
|
||||
@ -454,7 +455,7 @@ export class APISelect {
|
||||
let options = [] as Option[];
|
||||
|
||||
for (const result of data.results) {
|
||||
let text = result.display;
|
||||
let text = encode(result.display);
|
||||
|
||||
if (typeof result._depth === 'number' && result._depth > 0) {
|
||||
// If the object has a `_depth` property, indent its display text.
|
||||
|
@ -1818,6 +1818,11 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
html-entities@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
|
||||
integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
|
||||
|
||||
htmx.org@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/htmx.org/-/htmx.org-1.8.0.tgz#f3a2f681f3e2b6357b5a29bba24a2572a8e48fd3"
|
||||
|
@ -28,8 +28,22 @@
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Context Data</h5>
|
||||
<pre class="card-body">{{ context_data|pprint }}</pre>
|
||||
<div class="accordion accordion-flush" id="renderConfig">
|
||||
<div class="card-body">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="renderConfigHeading">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsedRenderConfig" aria-expanded="false" aria-controls="collapsedRenderConfig">
|
||||
Context Data
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapsedRenderConfig" class="accordion-collapse collapse" aria-labelledby="renderConfigHeading" data-bs-parent="#renderConfig">
|
||||
<div class="accordion-body">
|
||||
<pre class="card-body">{{ context_data|pprint }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -29,7 +29,10 @@ class ObjectContactsView(generic.ObjectChildrenView):
|
||||
def get_children(self, request, parent):
|
||||
return Contact.objects.annotate(
|
||||
assignment_count=count_related(ContactAssignment, 'contact')
|
||||
).restrict(request.user, 'view').filter(assignments__object_id=parent.pk)
|
||||
).restrict(request.user, 'view').filter(
|
||||
assignments__content_type=ContentType.objects.get_for_model(parent),
|
||||
assignments__object_id=parent.pk
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
|
@ -1,10 +1,18 @@
|
||||
import logging
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.signals import user_login_failed
|
||||
from utilities.request import get_client_ip
|
||||
|
||||
|
||||
@receiver(user_login_failed)
|
||||
def log_user_login_failed(sender, credentials, request, **kwargs):
|
||||
logger = logging.getLogger('netbox.auth.login')
|
||||
username = credentials.get("username")
|
||||
logger.info(f"Failed login attempt for username: {username}")
|
||||
if client_ip := get_client_ip(request):
|
||||
logger.info(f"Failed login attempt for username: {username} from {client_ip}")
|
||||
else:
|
||||
logger.warning(
|
||||
"Client IP address could not be determined for validation. Check that the HTTP server is properly "
|
||||
"configured to pass the required header(s)."
|
||||
)
|
||||
logger.info(f"Failed login attempt for username: {username}")
|
||||
|
Loading…
Reference in New Issue
Block a user