mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-17 13:08:16 -06:00
commit
7d7b3b5bcb
@ -113,7 +113,3 @@ svgwrite
|
|||||||
# Tabular dataset library (for table-based exports)
|
# Tabular dataset library (for table-based exports)
|
||||||
# https://github.com/jazzband/tablib
|
# https://github.com/jazzband/tablib
|
||||||
tablib
|
tablib
|
||||||
|
|
||||||
# It changes comma separated widget to list based in admin panel
|
|
||||||
# https://github.com/gradam/django-better-admin-arrayfield
|
|
||||||
django_better_admin_arrayfield
|
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#8233](https://github.com/netbox-community/netbox/issues/8233) - Restrict API key usage by source IP
|
* [#8233](https://github.com/netbox-community/netbox/issues/8233) - Restrict API key usage by source IP
|
||||||
|
* [#8553](https://github.com/netbox-community/netbox/issues/8553) - Add missing object types to global search form
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -10,11 +10,28 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
|||||||
A custom authentication scheme which enforces Token expiration times.
|
A custom authentication scheme which enforces Token expiration times.
|
||||||
"""
|
"""
|
||||||
model = Token
|
model = Token
|
||||||
__request = False
|
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
self.request = request
|
authenticationresult = super().authenticate(request)
|
||||||
return super().authenticate(request)
|
if authenticationresult:
|
||||||
|
token_user, token = authenticationresult
|
||||||
|
|
||||||
|
# Verify source IP is allowed
|
||||||
|
if token.allowed_ips:
|
||||||
|
# Replace 'HTTP_X_REAL_IP' with the settings variable choosen in #8867
|
||||||
|
if 'HTTP_X_REAL_IP' in request.META:
|
||||||
|
clientip = request.META['HTTP_X_REAL_IP'].split(",")[0].strip()
|
||||||
|
elif 'REMOTE_ADDR' in request.META:
|
||||||
|
clientip = request.META['REMOTE_ADDR']
|
||||||
|
else:
|
||||||
|
raise exceptions.AuthenticationFailed(f"A HTTP header containing the SourceIP (HTTP_X_REAL_IP, REMOTE_ADDR) is missing from the request.")
|
||||||
|
|
||||||
|
if not token.validate_client_ip(clientip):
|
||||||
|
raise exceptions.AuthenticationFailed(f"Source IP {clientip} is not allowed to use this token.")
|
||||||
|
|
||||||
|
return token_user, token
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def authenticate_credentials(self, key):
|
def authenticate_credentials(self, key):
|
||||||
model = self.get_model()
|
model = self.get_model()
|
||||||
@ -23,20 +40,6 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
|||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
raise exceptions.AuthenticationFailed("Invalid token")
|
raise exceptions.AuthenticationFailed("Invalid token")
|
||||||
|
|
||||||
# Verify source IP is allowed
|
|
||||||
request = self.request
|
|
||||||
if token.allowed_ips and request:
|
|
||||||
# Replace 'HTTP_X_REAL_IP' with the settings variable choosen in #8867
|
|
||||||
if 'HTTP_X_REAL_IP' in request.META:
|
|
||||||
clientip = request.META['HTTP_X_REAL_IP'].split(",")[0].strip()
|
|
||||||
elif 'REMOTE_ADDR' in request.META:
|
|
||||||
clientip = request.META['REMOTE_ADDR']
|
|
||||||
else:
|
|
||||||
raise exceptions.AuthenticationFailed(f"The request HTTP headers (HTTP_X_REAL_IP, REMOTE_ADDR) are missing or do not contain a valid source IP.")
|
|
||||||
|
|
||||||
if not token.validate_client_ip(clientip):
|
|
||||||
raise exceptions.AuthenticationFailed(f"Source IP {clientip} is not allowed to use this token.")
|
|
||||||
|
|
||||||
# Enforce the Token's expiration time, if one has been set.
|
# Enforce the Token's expiration time, if one has been set.
|
||||||
if token.is_expired:
|
if token.is_expired:
|
||||||
raise exceptions.AuthenticationFailed("Token expired")
|
raise exceptions.AuthenticationFailed("Token expired")
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from circuits.filtersets import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet
|
from circuits.filtersets import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet
|
||||||
from circuits.models import Circuit, ProviderNetwork, Provider
|
from circuits.models import Circuit, ProviderNetwork, Provider
|
||||||
@ -26,169 +27,212 @@ from virtualization.models import Cluster, VirtualMachine
|
|||||||
from virtualization.tables import ClusterTable, VirtualMachineTable
|
from virtualization.tables import ClusterTable, VirtualMachineTable
|
||||||
|
|
||||||
SEARCH_MAX_RESULTS = 15
|
SEARCH_MAX_RESULTS = 15
|
||||||
SEARCH_TYPES = OrderedDict((
|
|
||||||
# Circuits
|
CIRCUIT_TYPES = OrderedDict(
|
||||||
('provider', {
|
(
|
||||||
'queryset': Provider.objects.annotate(
|
('provider', {
|
||||||
count_circuits=count_related(Circuit, 'provider')
|
'queryset': Provider.objects.annotate(
|
||||||
),
|
count_circuits=count_related(Circuit, 'provider')
|
||||||
'filterset': ProviderFilterSet,
|
|
||||||
'table': ProviderTable,
|
|
||||||
'url': 'circuits:provider_list',
|
|
||||||
}),
|
|
||||||
('circuit', {
|
|
||||||
'queryset': Circuit.objects.prefetch_related(
|
|
||||||
'type', 'provider', 'tenant', 'terminations__site'
|
|
||||||
),
|
|
||||||
'filterset': CircuitFilterSet,
|
|
||||||
'table': CircuitTable,
|
|
||||||
'url': 'circuits:circuit_list',
|
|
||||||
}),
|
|
||||||
('providernetwork', {
|
|
||||||
'queryset': ProviderNetwork.objects.prefetch_related('provider'),
|
|
||||||
'filterset': ProviderNetworkFilterSet,
|
|
||||||
'table': ProviderNetworkTable,
|
|
||||||
'url': 'circuits:providernetwork_list',
|
|
||||||
}),
|
|
||||||
# DCIM
|
|
||||||
('site', {
|
|
||||||
'queryset': Site.objects.prefetch_related('region', 'tenant'),
|
|
||||||
'filterset': SiteFilterSet,
|
|
||||||
'table': SiteTable,
|
|
||||||
'url': 'dcim:site_list',
|
|
||||||
}),
|
|
||||||
('rack', {
|
|
||||||
'queryset': Rack.objects.prefetch_related('site', 'location', 'tenant', 'role'),
|
|
||||||
'filterset': RackFilterSet,
|
|
||||||
'table': RackTable,
|
|
||||||
'url': 'dcim:rack_list',
|
|
||||||
}),
|
|
||||||
('rackreservation', {
|
|
||||||
'queryset': RackReservation.objects.prefetch_related('site', 'rack', 'user'),
|
|
||||||
'filterset': RackReservationFilterSet,
|
|
||||||
'table': RackReservationTable,
|
|
||||||
'url': 'dcim:rackreservation_list',
|
|
||||||
}),
|
|
||||||
('location', {
|
|
||||||
'queryset': Location.objects.add_related_count(
|
|
||||||
Location.objects.add_related_count(
|
|
||||||
Location.objects.all(),
|
|
||||||
Device,
|
|
||||||
'location',
|
|
||||||
'device_count',
|
|
||||||
cumulative=True
|
|
||||||
),
|
),
|
||||||
Rack,
|
'filterset': ProviderFilterSet,
|
||||||
'location',
|
'table': ProviderTable,
|
||||||
'rack_count',
|
'url': 'circuits:provider_list',
|
||||||
cumulative=True
|
}),
|
||||||
).prefetch_related('site'),
|
('circuit', {
|
||||||
'filterset': LocationFilterSet,
|
'queryset': Circuit.objects.prefetch_related(
|
||||||
'table': LocationTable,
|
'type', 'provider', 'tenant', 'terminations__site'
|
||||||
'url': 'dcim:location_list',
|
),
|
||||||
}),
|
'filterset': CircuitFilterSet,
|
||||||
('devicetype', {
|
'table': CircuitTable,
|
||||||
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
'url': 'circuits:circuit_list',
|
||||||
instance_count=count_related(Device, 'device_type')
|
}),
|
||||||
),
|
('providernetwork', {
|
||||||
'filterset': DeviceTypeFilterSet,
|
'queryset': ProviderNetwork.objects.prefetch_related('provider'),
|
||||||
'table': DeviceTypeTable,
|
'filterset': ProviderNetworkFilterSet,
|
||||||
'url': 'dcim:devicetype_list',
|
'table': ProviderNetworkTable,
|
||||||
}),
|
'url': 'circuits:providernetwork_list',
|
||||||
('device', {
|
}),
|
||||||
'queryset': Device.objects.prefetch_related(
|
)
|
||||||
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6',
|
)
|
||||||
),
|
|
||||||
'filterset': DeviceFilterSet,
|
|
||||||
'table': DeviceTable,
|
DCIM_TYPES = OrderedDict(
|
||||||
'url': 'dcim:device_list',
|
(
|
||||||
}),
|
('site', {
|
||||||
('virtualchassis', {
|
'queryset': Site.objects.prefetch_related('region', 'tenant'),
|
||||||
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
|
'filterset': SiteFilterSet,
|
||||||
member_count=count_related(Device, 'virtual_chassis')
|
'table': SiteTable,
|
||||||
),
|
'url': 'dcim:site_list',
|
||||||
'filterset': VirtualChassisFilterSet,
|
}),
|
||||||
'table': VirtualChassisTable,
|
('rack', {
|
||||||
'url': 'dcim:virtualchassis_list',
|
'queryset': Rack.objects.prefetch_related('site', 'location', 'tenant', 'role'),
|
||||||
}),
|
'filterset': RackFilterSet,
|
||||||
('cable', {
|
'table': RackTable,
|
||||||
'queryset': Cable.objects.all(),
|
'url': 'dcim:rack_list',
|
||||||
'filterset': CableFilterSet,
|
}),
|
||||||
'table': CableTable,
|
('rackreservation', {
|
||||||
'url': 'dcim:cable_list',
|
'queryset': RackReservation.objects.prefetch_related('site', 'rack', 'user'),
|
||||||
}),
|
'filterset': RackReservationFilterSet,
|
||||||
('powerfeed', {
|
'table': RackReservationTable,
|
||||||
'queryset': PowerFeed.objects.all(),
|
'url': 'dcim:rackreservation_list',
|
||||||
'filterset': PowerFeedFilterSet,
|
}),
|
||||||
'table': PowerFeedTable,
|
('location', {
|
||||||
'url': 'dcim:powerfeed_list',
|
'queryset': Location.objects.add_related_count(
|
||||||
}),
|
Location.objects.add_related_count(
|
||||||
# Virtualization
|
Location.objects.all(),
|
||||||
('cluster', {
|
Device,
|
||||||
'queryset': Cluster.objects.prefetch_related('type', 'group').annotate(
|
'location',
|
||||||
device_count=count_related(Device, 'cluster'),
|
'device_count',
|
||||||
vm_count=count_related(VirtualMachine, 'cluster')
|
cumulative=True
|
||||||
),
|
),
|
||||||
'filterset': ClusterFilterSet,
|
Rack,
|
||||||
'table': ClusterTable,
|
'location',
|
||||||
'url': 'virtualization:cluster_list',
|
'rack_count',
|
||||||
}),
|
cumulative=True
|
||||||
('virtualmachine', {
|
).prefetch_related('site'),
|
||||||
'queryset': VirtualMachine.objects.prefetch_related(
|
'filterset': LocationFilterSet,
|
||||||
'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
'table': LocationTable,
|
||||||
),
|
'url': 'dcim:location_list',
|
||||||
'filterset': VirtualMachineFilterSet,
|
}),
|
||||||
'table': VirtualMachineTable,
|
('devicetype', {
|
||||||
'url': 'virtualization:virtualmachine_list',
|
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
}),
|
instance_count=count_related(Device, 'device_type')
|
||||||
# IPAM
|
),
|
||||||
('vrf', {
|
'filterset': DeviceTypeFilterSet,
|
||||||
'queryset': VRF.objects.prefetch_related('tenant'),
|
'table': DeviceTypeTable,
|
||||||
'filterset': VRFFilterSet,
|
'url': 'dcim:devicetype_list',
|
||||||
'table': VRFTable,
|
}),
|
||||||
'url': 'ipam:vrf_list',
|
('device', {
|
||||||
}),
|
'queryset': Device.objects.prefetch_related(
|
||||||
('aggregate', {
|
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6',
|
||||||
'queryset': Aggregate.objects.prefetch_related('rir'),
|
),
|
||||||
'filterset': AggregateFilterSet,
|
'filterset': DeviceFilterSet,
|
||||||
'table': AggregateTable,
|
'table': DeviceTable,
|
||||||
'url': 'ipam:aggregate_list',
|
'url': 'dcim:device_list',
|
||||||
}),
|
}),
|
||||||
('prefix', {
|
('virtualchassis', {
|
||||||
'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'),
|
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
|
||||||
'filterset': PrefixFilterSet,
|
member_count=count_related(Device, 'virtual_chassis')
|
||||||
'table': PrefixTable,
|
),
|
||||||
'url': 'ipam:prefix_list',
|
'filterset': VirtualChassisFilterSet,
|
||||||
}),
|
'table': VirtualChassisTable,
|
||||||
('ipaddress', {
|
'url': 'dcim:virtualchassis_list',
|
||||||
'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'),
|
}),
|
||||||
'filterset': IPAddressFilterSet,
|
('cable', {
|
||||||
'table': IPAddressTable,
|
'queryset': Cable.objects.all(),
|
||||||
'url': 'ipam:ipaddress_list',
|
'filterset': CableFilterSet,
|
||||||
}),
|
'table': CableTable,
|
||||||
('vlan', {
|
'url': 'dcim:cable_list',
|
||||||
'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role'),
|
}),
|
||||||
'filterset': VLANFilterSet,
|
('powerfeed', {
|
||||||
'table': VLANTable,
|
'queryset': PowerFeed.objects.all(),
|
||||||
'url': 'ipam:vlan_list',
|
'filterset': PowerFeedFilterSet,
|
||||||
}),
|
'table': PowerFeedTable,
|
||||||
('asn', {
|
'url': 'dcim:powerfeed_list',
|
||||||
'queryset': ASN.objects.prefetch_related('rir', 'tenant'),
|
}),
|
||||||
'filterset': ASNFilterSet,
|
)
|
||||||
'table': ASNTable,
|
)
|
||||||
'url': 'ipam:asn_list',
|
|
||||||
}),
|
IPAM_TYPES = OrderedDict(
|
||||||
# Tenancy
|
(
|
||||||
('tenant', {
|
('vrf', {
|
||||||
'queryset': Tenant.objects.prefetch_related('group'),
|
'queryset': VRF.objects.prefetch_related('tenant'),
|
||||||
'filterset': TenantFilterSet,
|
'filterset': VRFFilterSet,
|
||||||
'table': TenantTable,
|
'table': VRFTable,
|
||||||
'url': 'tenancy:tenant_list',
|
'url': 'ipam:vrf_list',
|
||||||
}),
|
}),
|
||||||
('contact', {
|
('aggregate', {
|
||||||
'queryset': Contact.objects.prefetch_related('group', 'assignments').annotate(assignment_count=count_related(ContactAssignment, 'contact')),
|
'queryset': Aggregate.objects.prefetch_related('rir'),
|
||||||
'filterset': ContactFilterSet,
|
'filterset': AggregateFilterSet,
|
||||||
'table': ContactTable,
|
'table': AggregateTable,
|
||||||
'url': 'tenancy:contact_list',
|
'url': 'ipam:aggregate_list',
|
||||||
}),
|
}),
|
||||||
))
|
('prefix', {
|
||||||
|
'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'),
|
||||||
|
'filterset': PrefixFilterSet,
|
||||||
|
'table': PrefixTable,
|
||||||
|
'url': 'ipam:prefix_list',
|
||||||
|
}),
|
||||||
|
('ipaddress', {
|
||||||
|
'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'),
|
||||||
|
'filterset': IPAddressFilterSet,
|
||||||
|
'table': IPAddressTable,
|
||||||
|
'url': 'ipam:ipaddress_list',
|
||||||
|
}),
|
||||||
|
('vlan', {
|
||||||
|
'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role'),
|
||||||
|
'filterset': VLANFilterSet,
|
||||||
|
'table': VLANTable,
|
||||||
|
'url': 'ipam:vlan_list',
|
||||||
|
}),
|
||||||
|
('asn', {
|
||||||
|
'queryset': ASN.objects.prefetch_related('rir', 'tenant'),
|
||||||
|
'filterset': ASNFilterSet,
|
||||||
|
'table': ASNTable,
|
||||||
|
'url': 'ipam:asn_list',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
TENANCY_TYPES = OrderedDict(
|
||||||
|
(
|
||||||
|
('tenant', {
|
||||||
|
'queryset': Tenant.objects.prefetch_related('group'),
|
||||||
|
'filterset': TenantFilterSet,
|
||||||
|
'table': TenantTable,
|
||||||
|
'url': 'tenancy:tenant_list',
|
||||||
|
}),
|
||||||
|
('contact', {
|
||||||
|
'queryset': Contact.objects.prefetch_related('group', 'assignments').annotate(
|
||||||
|
assignment_count=count_related(ContactAssignment, 'contact')),
|
||||||
|
'filterset': ContactFilterSet,
|
||||||
|
'table': ContactTable,
|
||||||
|
'url': 'tenancy:contact_list',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
VIRTUALIZATION_TYPES = OrderedDict(
|
||||||
|
(
|
||||||
|
('cluster', {
|
||||||
|
'queryset': Cluster.objects.prefetch_related('type', 'group').annotate(
|
||||||
|
device_count=count_related(Device, 'cluster'),
|
||||||
|
vm_count=count_related(VirtualMachine, 'cluster')
|
||||||
|
),
|
||||||
|
'filterset': ClusterFilterSet,
|
||||||
|
'table': ClusterTable,
|
||||||
|
'url': 'virtualization:cluster_list',
|
||||||
|
}),
|
||||||
|
('virtualmachine', {
|
||||||
|
'queryset': VirtualMachine.objects.prefetch_related(
|
||||||
|
'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
||||||
|
),
|
||||||
|
'filterset': VirtualMachineFilterSet,
|
||||||
|
'table': VirtualMachineTable,
|
||||||
|
'url': 'virtualization:virtualmachine_list',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
SEARCH_TYPE_HIERARCHY = OrderedDict(
|
||||||
|
(
|
||||||
|
("Circuits", CIRCUIT_TYPES),
|
||||||
|
("DCIM", DCIM_TYPES),
|
||||||
|
("IPAM", IPAM_TYPES),
|
||||||
|
("Tenancy", TENANCY_TYPES),
|
||||||
|
("Virtualization", VIRTUALIZATION_TYPES),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_search_types() -> Dict[str, Dict]:
|
||||||
|
result = dict()
|
||||||
|
|
||||||
|
for app_types in SEARCH_TYPE_HIERARCHY.values():
|
||||||
|
for name, items in app_types.items():
|
||||||
|
result[name] = items
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
SEARCH_TYPES = build_search_types()
|
||||||
|
@ -1,39 +1,24 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
|
from netbox.constants import SEARCH_TYPE_HIERARCHY
|
||||||
|
|
||||||
OBJ_TYPE_CHOICES = (
|
|
||||||
('', 'All Objects'),
|
def build_search_choices():
|
||||||
('Circuits', (
|
result = list()
|
||||||
('provider', 'Providers'),
|
result.append(('', 'All Objects'))
|
||||||
('circuit', 'Circuits'),
|
for category, items in SEARCH_TYPE_HIERARCHY.items():
|
||||||
)),
|
subcategories = list()
|
||||||
('DCIM', (
|
for slug, obj in items.items():
|
||||||
('site', 'Sites'),
|
name = obj['queryset'].model._meta.verbose_name_plural
|
||||||
('rack', 'Racks'),
|
name = name[0].upper() + name[1:]
|
||||||
('rackreservation', 'Rack reservations'),
|
subcategories.append((slug, name))
|
||||||
('location', 'Locations'),
|
result.append((category, tuple(subcategories)))
|
||||||
('devicetype', 'Device Types'),
|
|
||||||
('device', 'Devices'),
|
return tuple(result)
|
||||||
('virtualchassis', 'Virtual chassis'),
|
|
||||||
('cable', 'Cables'),
|
|
||||||
('powerfeed', 'Power feeds'),
|
OBJ_TYPE_CHOICES = build_search_choices()
|
||||||
)),
|
|
||||||
('IPAM', (
|
|
||||||
('vrf', 'VRFs'),
|
|
||||||
('aggregate', 'Aggregates'),
|
|
||||||
('prefix', 'Prefixes'),
|
|
||||||
('ipaddress', 'IP Addresses'),
|
|
||||||
('vlan', 'VLANs'),
|
|
||||||
)),
|
|
||||||
('Tenancy', (
|
|
||||||
('tenant', 'Tenants'),
|
|
||||||
)),
|
|
||||||
('Virtualization', (
|
|
||||||
('cluster', 'Clusters'),
|
|
||||||
('virtualmachine', 'Virtual Machines'),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_options():
|
def build_options():
|
||||||
|
@ -321,7 +321,6 @@ INSTALLED_APPS = [
|
|||||||
'wireless',
|
'wireless',
|
||||||
'django_rq', # Must come after extras to allow overriding management commands
|
'django_rq', # Must come after extras to allow overriding management commands
|
||||||
'drf_yasg',
|
'drf_yasg',
|
||||||
'django_better_admin_arrayfield',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Middleware
|
# Middleware
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as UserAdmin_
|
from django.contrib.auth.admin import UserAdmin as UserAdmin_
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
|
|
||||||
|
|
||||||
from users.models import ObjectPermission, Token
|
from users.models import ObjectPermission, Token
|
||||||
from . import filters, forms, inlines
|
from . import filters, forms, inlines
|
||||||
@ -56,15 +55,16 @@ class UserAdmin(UserAdmin_):
|
|||||||
#
|
#
|
||||||
|
|
||||||
@admin.register(Token)
|
@admin.register(Token)
|
||||||
class TokenAdmin(admin.ModelAdmin, DynamicArrayMixin):
|
class TokenAdmin(admin.ModelAdmin):
|
||||||
form = forms.TokenAdminForm
|
form = forms.TokenAdminForm
|
||||||
list_display = [
|
list_display = [
|
||||||
'key', 'user', 'created', 'expires', 'write_enabled', 'description', 'list_allowed_ips'
|
'key', 'user', 'created', 'expires', 'write_enabled', 'description', 'list_allowed_ips'
|
||||||
]
|
]
|
||||||
|
|
||||||
def list_allowed_ips(self, obj):
|
def list_allowed_ips(self, obj):
|
||||||
return obj.allowed_ips
|
if obj.allowed_ips:
|
||||||
list_allowed_ips.empty_value_display = 'Any'
|
return obj.allowed_ips
|
||||||
|
return 'Any'
|
||||||
list_allowed_ips.short_description = "Allowed IPs"
|
list_allowed_ips.short_description = "Allowed IPs"
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Generated by Django 3.2.12 on 2022-03-15 13:08
|
# Generated by Django 3.2.12 on 2022-03-18 08:25
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import django_better_admin_arrayfield.models.fields
|
|
||||||
import ipam.fields
|
import ipam.fields
|
||||||
|
|
||||||
|
|
||||||
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='token',
|
model_name='token',
|
||||||
name='allowed_ips',
|
name='allowed_ips',
|
||||||
field=django_better_admin_arrayfield.models.fields.ArrayField(base_field=ipam.fields.IPNetworkField(), blank=True, null=True, size=None),
|
field=django.contrib.postgres.fields.ArrayField(base_field=ipam.fields.IPNetworkField(), blank=True, null=True, size=None),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,7 +10,6 @@ from django.db import models
|
|||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_better_admin_arrayfield.models.fields import ArrayField as betterArrayField
|
|
||||||
|
|
||||||
from netbox.models import BigIDModel
|
from netbox.models import BigIDModel
|
||||||
from ipam.fields import IPNetworkField
|
from ipam.fields import IPNetworkField
|
||||||
@ -208,7 +207,7 @@ class Token(BigIDModel):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
allowed_ips = betterArrayField(
|
allowed_ips = ArrayField(
|
||||||
base_field=IPNetworkField(),
|
base_field=IPNetworkField(),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
@ -28,7 +28,6 @@ social-auth-core==4.2.0
|
|||||||
svgwrite==1.4.1
|
svgwrite==1.4.1
|
||||||
tablib==3.2.0
|
tablib==3.2.0
|
||||||
tzdata==2021.5
|
tzdata==2021.5
|
||||||
django_better_admin_arrayfield==1.4.2
|
|
||||||
|
|
||||||
# Workaround for #7401
|
# Workaround for #7401
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user