mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-02 05:46:25 -06:00
Merge branch 'feature' into 17476-upgrade-django51
This commit is contained in:
commit
58d3838c4b
@ -96,14 +96,6 @@ The maximum size (in bytes) of an incoming HTTP request (i.e. `GET` or `POST` da
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## DJANGO_ADMIN_ENABLED
|
|
||||||
|
|
||||||
Default: False
|
|
||||||
|
|
||||||
Setting this to True installs the `django.contrib.admin` app and enables the [Django admin UI](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/). This may be necessary to support older plugins which do not integrate with the native NetBox interface.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ENFORCE_GLOBAL_UNIQUE
|
## ENFORCE_GLOBAL_UNIQUE
|
||||||
|
|
||||||
!!! tip "Dynamic Configuration Parameter"
|
!!! tip "Dynamic Configuration Parameter"
|
||||||
|
@ -44,3 +44,7 @@ The serial number assigned by the manufacturer.
|
|||||||
### Asset Tag
|
### Asset Tag
|
||||||
|
|
||||||
A unique, locally-administered label used to identify hardware resources.
|
A unique, locally-administered label used to identify hardware resources.
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
The inventory item's operational status.
|
||||||
|
@ -29,6 +29,10 @@ An alternative physical label identifying the power outlet.
|
|||||||
|
|
||||||
The type of power outlet.
|
The type of power outlet.
|
||||||
|
|
||||||
|
### Color
|
||||||
|
|
||||||
|
The power outlet's color (optional).
|
||||||
|
|
||||||
### Power Port
|
### Power Port
|
||||||
|
|
||||||
When modeling a device which redistributes power from an upstream supply, such as a power distribution unit (PDU), each power outlet should be mapped to the respective [power port](./powerport.md) on the device which supplies power. For example, a 24-outlet PDU may two power ports, each distributing power to 12 of its outlets.
|
When modeling a device which redistributes power from an upstream supply, such as a power distribution unit (PDU), each power outlet should be mapped to the respective [power port](./powerport.md) on the device which supplies power. For example, a 24-outlet PDU may two power ports, each distributing power to 12 of its outlets.
|
||||||
|
@ -155,7 +155,7 @@ class PowerOutletSerializer(NetBoxModelSerializer, CabledObjectSerializer, Conne
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'power_port',
|
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'power_port',
|
||||||
'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||||
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields',
|
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', '_occupied',
|
'created', 'last_updated', '_occupied',
|
||||||
@ -345,11 +345,12 @@ class InventoryItemSerializer(NetBoxModelSerializer):
|
|||||||
)
|
)
|
||||||
component = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
component = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||||
|
status = ChoiceField(choices=InventoryItemStatusChoices, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'role', 'manufacturer',
|
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role', 'manufacturer',
|
||||||
'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type', 'component_id',
|
'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type', 'component_id',
|
||||||
'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
|
'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
|
||||||
]
|
]
|
||||||
|
@ -1648,3 +1648,27 @@ class VirtualDeviceContextStatusChoices(ChoiceSet):
|
|||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
||||||
(STATUS_OFFLINE, _('Offline'), 'red'),
|
(STATUS_OFFLINE, _('Offline'), 'red'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# InventoryItem
|
||||||
|
#
|
||||||
|
|
||||||
|
class InventoryItemStatusChoices(ChoiceSet):
|
||||||
|
key = 'InventoryItem.status'
|
||||||
|
|
||||||
|
STATUS_OFFLINE = 'offline'
|
||||||
|
STATUS_ACTIVE = 'active'
|
||||||
|
STATUS_PLANNED = 'planned'
|
||||||
|
STATUS_STAGED = 'staged'
|
||||||
|
STATUS_FAILED = 'failed'
|
||||||
|
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||||
|
|
||||||
|
CHOICES = [
|
||||||
|
(STATUS_OFFLINE, _('Offline'), 'gray'),
|
||||||
|
(STATUS_ACTIVE, _('Active'), 'green'),
|
||||||
|
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
||||||
|
(STATUS_STAGED, _('Staged'), 'blue'),
|
||||||
|
(STATUS_FAILED, _('Failed'), 'red'),
|
||||||
|
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
|
||||||
|
]
|
||||||
|
@ -1594,7 +1594,7 @@ class PowerOutletFilterSet(
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'label', 'feed_leg', 'description', 'mark_connected', 'cable_end',
|
'id', 'name', 'label', 'feed_leg', 'description', 'color', 'mark_connected', 'cable_end',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1860,10 +1860,14 @@ class InventoryItemFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
|||||||
serial = MultiValueCharFilter(
|
serial = MultiValueCharFilter(
|
||||||
lookup_expr='iexact'
|
lookup_expr='iexact'
|
||||||
)
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=InventoryItemStatusChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = ('id', 'name', 'label', 'part_id', 'asset_tag', 'description', 'discovered')
|
fields = ('id', 'name', 'label', 'part_id', 'asset_tag', 'status', 'description', 'discovered')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -69,7 +69,7 @@ class PowerPortBulkCreateForm(
|
|||||||
|
|
||||||
|
|
||||||
class PowerOutletBulkCreateForm(
|
class PowerOutletBulkCreateForm(
|
||||||
form_from_model(PowerOutlet, ['type', 'feed_leg', 'mark_connected']),
|
form_from_model(PowerOutlet, ['type', 'color', 'feed_leg', 'mark_connected']),
|
||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
|
@ -1361,7 +1361,7 @@ class PowerPortBulkEditForm(
|
|||||||
|
|
||||||
class PowerOutletBulkEditForm(
|
class PowerOutletBulkEditForm(
|
||||||
ComponentBulkEditForm,
|
ComponentBulkEditForm,
|
||||||
form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description'])
|
form_from_model(PowerOutlet, ['label', 'type', 'color', 'feed_leg', 'power_port', 'mark_connected', 'description'])
|
||||||
):
|
):
|
||||||
mark_connected = forms.NullBooleanField(
|
mark_connected = forms.NullBooleanField(
|
||||||
label=_('Mark connected'),
|
label=_('Mark connected'),
|
||||||
@ -1371,7 +1371,7 @@ class PowerOutletBulkEditForm(
|
|||||||
|
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
|
FieldSet('module', 'type', 'label', 'description', 'mark_connected', 'color'),
|
||||||
FieldSet('feed_leg', 'power_port', name=_('Power')),
|
FieldSet('feed_leg', 'power_port', name=_('Power')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
|
nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
|
||||||
@ -1661,10 +1661,16 @@ class InventoryItemBulkEditForm(
|
|||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
status = forms.ChoiceField(
|
||||||
|
label=_('Status'),
|
||||||
|
choices=add_blank_choice(InventoryItemStatusChoices),
|
||||||
|
required=False,
|
||||||
|
initial=''
|
||||||
|
)
|
||||||
|
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'description'),
|
FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'status', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
||||||
|
|
||||||
|
@ -798,7 +798,7 @@ class PowerOutletImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = ('device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags')
|
fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -1103,11 +1103,16 @@ class InventoryItemImportForm(NetBoxModelImportForm):
|
|||||||
required=False,
|
required=False,
|
||||||
help_text=_('Component Name')
|
help_text=_('Component Name')
|
||||||
)
|
)
|
||||||
|
status = CSVChoiceField(
|
||||||
|
label=_('Status'),
|
||||||
|
choices=InventoryItemStatusChoices,
|
||||||
|
help_text=_('Operational status')
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = (
|
fields = (
|
||||||
'device', 'name', 'label', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'discovered',
|
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||||
'description', 'tags', 'component_type', 'component_name',
|
'description', 'tags', 'component_type', 'component_name',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ __all__ = (
|
|||||||
'LocationFilterForm',
|
'LocationFilterForm',
|
||||||
'ManufacturerFilterForm',
|
'ManufacturerFilterForm',
|
||||||
'ModuleFilterForm',
|
'ModuleFilterForm',
|
||||||
'ModuleFilterForm',
|
|
||||||
'ModuleBayFilterForm',
|
'ModuleBayFilterForm',
|
||||||
'ModuleTypeFilterForm',
|
'ModuleTypeFilterForm',
|
||||||
'PlatformFilterForm',
|
'PlatformFilterForm',
|
||||||
@ -1304,7 +1303,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('name', 'label', 'type', name=_('Attributes')),
|
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
|
'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
|
||||||
@ -1318,6 +1317,10 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
color = ColorField(
|
||||||
|
label=_('Color'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
@ -1553,6 +1556,11 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
|
|||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
status = forms.MultipleChoiceField(
|
||||||
|
label=_('Status'),
|
||||||
|
choices=InventoryItemStatusChoices,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1285,7 +1285,7 @@ class PowerOutletForm(ModularDeviceComponentForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
||||||
'tags',
|
'tags',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1293,7 +1293,7 @@ class PowerOutletForm(ModularDeviceComponentForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1576,7 +1576,7 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('device', 'parent', 'name', 'label', 'role', 'description', 'tags', name=_('Inventory Item')),
|
FieldSet('device', 'parent', 'name', 'label', 'status', 'role', 'description', 'tags', name=_('Inventory Item')),
|
||||||
FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')),
|
FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
TabbedGroups(
|
TabbedGroups(
|
||||||
@ -1596,7 +1596,7 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||||
'description', 'tags',
|
'status', 'description', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -568,6 +568,7 @@ class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin):
|
|||||||
)
|
)
|
||||||
class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
|
class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
|
||||||
power_port: Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')] | None
|
power_port: Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')] | None
|
||||||
|
color: str
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
|
18
netbox/dcim/migrations/0191_inventoryitem_status.py
Normal file
18
netbox/dcim/migrations/0191_inventoryitem_status.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.9 on 2024-09-26 20:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0190_nested_modules'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='active', max_length=50),
|
||||||
|
),
|
||||||
|
]
|
19
netbox/dcim/migrations/0191_poweroutlet_color.py
Normal file
19
netbox/dcim/migrations/0191_poweroutlet_color.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.0.9 on 2024-09-26 19:31
|
||||||
|
|
||||||
|
import utilities.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0190_nested_modules'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='color',
|
||||||
|
field=utilities.fields.ColorField(blank=True, max_length=6),
|
||||||
|
),
|
||||||
|
]
|
@ -481,6 +481,10 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Phase (for three-phase feeds)')
|
help_text=_('Phase (for three-phase feeds)')
|
||||||
)
|
)
|
||||||
|
color = ColorField(
|
||||||
|
verbose_name=_('color'),
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
|
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
|
||||||
|
|
||||||
@ -1244,6 +1248,12 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
ct_field='component_type',
|
ct_field='component_type',
|
||||||
fk_field='component_id'
|
fk_field='component_id'
|
||||||
)
|
)
|
||||||
|
status = models.CharField(
|
||||||
|
verbose_name=_('status'),
|
||||||
|
max_length=50,
|
||||||
|
choices=InventoryItemStatusChoices,
|
||||||
|
default=InventoryItemStatusChoices.STATUS_ACTIVE
|
||||||
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
to='dcim.InventoryItemRole',
|
to='dcim.InventoryItemRole',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -1285,7 +1295,7 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
|
|
||||||
objects = TreeManager()
|
objects = TreeManager()
|
||||||
|
|
||||||
clone_fields = ('device', 'parent', 'role', 'manufacturer', 'part_id',)
|
clone_fields = ('device', 'parent', 'role', 'manufacturer', 'status', 'part_id')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device__id', 'parent__id', '_name')
|
ordering = ('device__id', 'parent__id', '_name')
|
||||||
@ -1334,3 +1344,6 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"device": _("Cannot assign inventory item to component on another device")
|
"device": _("Cannot assign inventory item to component on another device")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_status_color(self):
|
||||||
|
return InventoryItemStatusChoices.colors.get(self.status)
|
||||||
|
@ -512,6 +512,7 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
|
|||||||
verbose_name=_('Power Port'),
|
verbose_name=_('Power Port'),
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
color = columns.ColorColumn()
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:poweroutlet_list'
|
url_name='dcim:poweroutlet_list'
|
||||||
)
|
)
|
||||||
@ -520,10 +521,10 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
|
|||||||
model = models.PowerOutlet
|
model = models.PowerOutlet
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
|
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
|
||||||
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items',
|
'color', 'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items',
|
||||||
'tags', 'created', 'last_updated',
|
'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description')
|
||||||
|
|
||||||
|
|
||||||
class DevicePowerOutletTable(PowerOutletTable):
|
class DevicePowerOutletTable(PowerOutletTable):
|
||||||
@ -540,11 +541,11 @@ class DevicePowerOutletTable(PowerOutletTable):
|
|||||||
class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta):
|
class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta):
|
||||||
model = models.PowerOutlet
|
model = models.PowerOutlet
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'color', 'power_port', 'feed_leg',
|
||||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection',
|
'pk', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description', 'cable', 'connection',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -946,6 +947,9 @@ class InventoryItemTable(DeviceComponentTable):
|
|||||||
verbose_name=_('Discovered'),
|
verbose_name=_('Discovered'),
|
||||||
false_mark=None
|
false_mark=None
|
||||||
)
|
)
|
||||||
|
status = columns.ChoiceFieldColumn(
|
||||||
|
verbose_name=_('Status'),
|
||||||
|
)
|
||||||
parent = tables.Column(
|
parent = tables.Column(
|
||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name=_('Parent'),
|
verbose_name=_('Parent'),
|
||||||
@ -958,11 +962,11 @@ class InventoryItemTable(DeviceComponentTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = models.InventoryItem
|
model = models.InventoryItem
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'device', 'parent', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial',
|
'pk', 'id', 'name', 'device', 'parent', 'component', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial',
|
||||||
'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated',
|
'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'device', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
'pk', 'name', 'device', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -978,11 +982,11 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = models.InventoryItem
|
model = models.InventoryItem
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
'pk', 'id', 'name', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||||
'description', 'discovered', 'tags', 'actions',
|
'description', 'discovered', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
'pk', 'name', 'label', 'status', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3421,9 +3421,9 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
|
|||||||
PowerPort.objects.bulk_create(power_ports)
|
PowerPort.objects.bulk_create(power_ports)
|
||||||
|
|
||||||
power_outlets = (
|
power_outlets = (
|
||||||
PowerOutlet(device=devices[0], module=modules[0], name='Power Outlet 1', label='A', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A, description='First'),
|
PowerOutlet(device=devices[0], module=modules[0], name='Power Outlet 1', label='A', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A, description='First', color='ff0000'),
|
||||||
PowerOutlet(device=devices[1], module=modules[1], name='Power Outlet 2', label='B', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B, description='Second'),
|
PowerOutlet(device=devices[1], module=modules[1], name='Power Outlet 2', label='B', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B, description='Second', color='00ff00'),
|
||||||
PowerOutlet(device=devices[2], module=modules[2], name='Power Outlet 3', label='C', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C, description='Third'),
|
PowerOutlet(device=devices[2], module=modules[2], name='Power Outlet 3', label='C', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C, description='Third', color='0000ff'),
|
||||||
)
|
)
|
||||||
PowerOutlet.objects.bulk_create(power_outlets)
|
PowerOutlet.objects.bulk_create(power_outlets)
|
||||||
|
|
||||||
@ -3444,6 +3444,10 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
|
|||||||
params = {'description': ['First', 'Second']}
|
params = {'description': ['First', 'Second']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_color(self):
|
||||||
|
params = {'color': ['ff0000', '00ff00']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_feed_leg(self):
|
def test_feed_leg(self):
|
||||||
params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]}
|
params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -4751,9 +4755,9 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
|
|
||||||
inventory_items = (
|
inventory_items = (
|
||||||
InventoryItem(device=devices[0], role=roles[0], manufacturer=manufacturers[0], name='Inventory Item 1', label='A', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, description='First', component=components[0]),
|
InventoryItem(device=devices[0], role=roles[0], manufacturer=manufacturers[0], name='Inventory Item 1', label='A', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, status=ModuleStatusChoices.STATUS_ACTIVE, description='First', component=components[0]),
|
||||||
InventoryItem(device=devices[1], role=roles[1], manufacturer=manufacturers[1], name='Inventory Item 2', label='B', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second', component=components[1]),
|
InventoryItem(device=devices[1], role=roles[1], manufacturer=manufacturers[1], name='Inventory Item 2', label='B', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, status=ModuleStatusChoices.STATUS_PLANNED, description='Second', component=components[1]),
|
||||||
InventoryItem(device=devices[2], role=roles[2], manufacturer=manufacturers[2], name='Inventory Item 3', label='C', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third', component=components[2]),
|
InventoryItem(device=devices[2], role=roles[2], manufacturer=manufacturers[2], name='Inventory Item 3', label='C', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, status=ModuleStatusChoices.STATUS_FAILED, description='Third', component=components[2]),
|
||||||
)
|
)
|
||||||
for i in inventory_items:
|
for i in inventory_items:
|
||||||
i.save()
|
i.save()
|
||||||
@ -4881,6 +4885,10 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'component_type': 'dcim.interface'}
|
params = {'component_type': 'dcim.interface'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_status(self):
|
||||||
|
params = {'status': [InventoryItemStatusChoices.STATUS_PLANNED, InventoryItemStatusChoices.STATUS_FAILED]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class InventoryItemRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = InventoryItemRole.objects.all()
|
queryset = InventoryItemRole.objects.all()
|
||||||
|
@ -2903,6 +2903,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
'part_id': '123456',
|
'part_id': '123456',
|
||||||
'serial': '123ABC',
|
'serial': '123ABC',
|
||||||
'asset_tag': 'ABC123',
|
'asset_tag': 'ABC123',
|
||||||
|
'status': InventoryItemStatusChoices.STATUS_ACTIVE,
|
||||||
'description': 'An inventory item',
|
'description': 'An inventory item',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2916,6 +2917,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
'discovered': False,
|
'discovered': False,
|
||||||
'part_id': '123456',
|
'part_id': '123456',
|
||||||
'serial': '123ABC',
|
'serial': '123ABC',
|
||||||
|
'status': InventoryItemStatusChoices.STATUS_ACTIVE,
|
||||||
'description': 'An inventory item',
|
'description': 'An inventory item',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2927,10 +2929,10 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"device,name,parent",
|
"device,name,parent,status",
|
||||||
"Device 1,Inventory Item 4,Inventory Item 1",
|
"Device 1,Inventory Item 4,Inventory Item 1,active",
|
||||||
"Device 1,Inventory Item 5,Inventory Item 2",
|
"Device 1,Inventory Item 5,Inventory Item 2,planned",
|
||||||
"Device 1,Inventory Item 6,Inventory Item 3",
|
"Device 1,Inventory Item 6,Inventory Item 3,failed",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.contrib.admin import site as admin_site
|
|
||||||
from taggit.models import Tag
|
|
||||||
|
|
||||||
|
|
||||||
# Override default AdminSite attributes so we can avoid creating and
|
|
||||||
# registering our own class
|
|
||||||
admin_site.site_header = 'NetBox Administration'
|
|
||||||
admin_site.site_title = 'NetBox'
|
|
||||||
admin_site.site_url = '/{}'.format(settings.BASE_PATH)
|
|
||||||
admin_site.index_template = 'admin/index.html'
|
|
||||||
|
|
||||||
# Unregister the unused stock Tag model provided by django-taggit
|
|
||||||
admin_site.unregister(Tag)
|
|
@ -39,8 +39,6 @@ REDIS = {
|
|||||||
|
|
||||||
SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
|
||||||
DJANGO_ADMIN_ENABLED = True
|
|
||||||
|
|
||||||
DEFAULT_PERMISSIONS = {}
|
DEFAULT_PERMISSIONS = {}
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
@ -3,13 +3,11 @@ from importlib import import_module
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.utils.module_loading import import_string, module_has_submodule
|
from django.utils.module_loading import import_string, module_has_submodule
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
# Initialize URL base, API, and admin URL patterns for plugins
|
|
||||||
plugin_patterns = []
|
plugin_patterns = []
|
||||||
plugin_api_patterns = [
|
plugin_api_patterns = [
|
||||||
path('', views.PluginsAPIRootView.as_view(), name='api-root'),
|
path('', views.PluginsAPIRootView.as_view(), name='api-root'),
|
||||||
|
@ -110,7 +110,6 @@ DEFAULT_PERMISSIONS = getattr(configuration, 'DEFAULT_PERMISSIONS', {
|
|||||||
'users.delete_token': ({'user': '$user'},),
|
'users.delete_token': ({'user': '$user'},),
|
||||||
})
|
})
|
||||||
DEVELOPER = getattr(configuration, 'DEVELOPER', False)
|
DEVELOPER = getattr(configuration, 'DEVELOPER', False)
|
||||||
DJANGO_ADMIN_ENABLED = getattr(configuration, 'DJANGO_ADMIN_ENABLED', False)
|
|
||||||
DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
|
DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
|
||||||
EMAIL = getattr(configuration, 'EMAIL', {})
|
EMAIL = getattr(configuration, 'EMAIL', {})
|
||||||
EVENTS_PIPELINE = getattr(configuration, 'EVENTS_PIPELINE', (
|
EVENTS_PIPELINE = getattr(configuration, 'EVENTS_PIPELINE', (
|
||||||
@ -373,7 +372,6 @@ SERVER_EMAIL = EMAIL.get('FROM_EMAIL')
|
|||||||
#
|
#
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
@ -411,8 +409,6 @@ INSTALLED_APPS = [
|
|||||||
]
|
]
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
INSTALLED_APPS.remove('debug_toolbar')
|
INSTALLED_APPS.remove('debug_toolbar')
|
||||||
if not DJANGO_ADMIN_ENABLED:
|
|
||||||
INSTALLED_APPS.remove('django.contrib.admin')
|
|
||||||
|
|
||||||
# Middleware
|
# Middleware
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@ -549,7 +545,6 @@ EXEMPT_EXCLUDE_MODELS = (
|
|||||||
|
|
||||||
# All URLs starting with a string listed here are exempt from maintenance mode enforcement
|
# All URLs starting with a string listed here are exempt from maintenance mode enforcement
|
||||||
MAINTENANCE_EXEMPT_PATHS = (
|
MAINTENANCE_EXEMPT_PATHS = (
|
||||||
f'/{BASE_PATH}admin/',
|
|
||||||
f'/{BASE_PATH}extras/config-revisions/', # Allow modifying the configuration
|
f'/{BASE_PATH}extras/config-revisions/', # Allow modifying the configuration
|
||||||
LOGIN_URL,
|
LOGIN_URL,
|
||||||
LOGIN_REDIRECT_URL,
|
LOGIN_REDIRECT_URL,
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from netbox.admin import admin_site
|
|
||||||
from .models import DummyModel
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DummyModel, site=admin_site)
|
|
||||||
class DummyModelAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'number')
|
|
@ -36,12 +36,6 @@ class PluginTest(TestCase):
|
|||||||
instance.delete()
|
instance.delete()
|
||||||
self.assertIsNone(instance.pk)
|
self.assertIsNone(instance.pk)
|
||||||
|
|
||||||
def test_admin(self):
|
|
||||||
|
|
||||||
# Test admin view URL resolution
|
|
||||||
url = reverse('admin:dummy_plugin_dummymodel_add')
|
|
||||||
self.assertEqual(url, '/admin/dummy_plugin/dummymodel/add/')
|
|
||||||
|
|
||||||
@override_settings(LOGIN_REQUIRED=False)
|
@override_settings(LOGIN_REQUIRED=False)
|
||||||
def test_views(self):
|
def test_views(self):
|
||||||
|
|
||||||
|
@ -77,11 +77,6 @@ _patterns = [
|
|||||||
path('api/plugins/', include((plugin_api_patterns, 'plugins-api'))),
|
path('api/plugins/', include((plugin_api_patterns, 'plugins-api'))),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Django admin UI
|
|
||||||
if settings.DJANGO_ADMIN_ENABLED:
|
|
||||||
from .admin import admin_site
|
|
||||||
_patterns.append(path('admin/', admin_site.urls))
|
|
||||||
|
|
||||||
# django-debug-toolbar
|
# django-debug-toolbar
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
|
@ -56,6 +56,10 @@
|
|||||||
<th scope="row">{% trans "Asset Tag" %}</th>
|
<th scope="row">{% trans "Asset Tag" %}</th>
|
||||||
<td>{{ object.asset_tag|placeholder }}</td>
|
<td>{{ object.asset_tag|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Status" %}</th>
|
||||||
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
@ -40,6 +40,16 @@
|
|||||||
<th scope="row">{% trans "Description" %}</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Color" %}</th>
|
||||||
|
<td>
|
||||||
|
{% if object.color %}
|
||||||
|
<span class="badge color-label" style="background-color: #{{ object.color }}"> </span>
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Power Port" %}</th>
|
<th scope="row">{% trans "Power Port" %}</th>
|
||||||
<td>{{ object.power_port|linkify|placeholder }}</td>
|
<td>{{ object.power_port|linkify|placeholder }}</td>
|
||||||
|
@ -36,11 +36,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow" {% htmx_boost %}>
|
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow" {% htmx_boost %}>
|
||||||
{% if config.DJANGO_ADMIN_ENABLED and request.user.is_staff %}
|
|
||||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
|
||||||
<i class="mdi mdi-cog"></i> {% trans "Django Admin" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'account:profile' %}" class="dropdown-item">
|
<a href="{% url 'account:profile' %}" class="dropdown-item">
|
||||||
<i class="mdi mdi-account"></i> {% trans "Profile" %}
|
<i class="mdi mdi-account"></i> {% trans "Profile" %}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth.models import Group as DjangoGroup
|
|
||||||
|
|
||||||
# Prevent the stock Django Group model from appearing in the admin UI (if enabled)
|
|
||||||
admin.site.unregister(DjangoGroup)
|
|
Loading…
Reference in New Issue
Block a user