mirror of
https://github.com/netbox-community/netbox.git
synced 2026-03-21 20:18:38 -06:00
Add a RackReservation.unit_count calculated field and filtering functionality
This commit is contained in:
@@ -173,11 +173,16 @@ class RackReservationSerializer(PrimaryModelSerializer):
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
unit_count = serializers.SerializerMethodField()
|
||||
|
||||
def get_unit_count(self, obj):
|
||||
return len(obj.units)
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'rack', 'units', 'status', 'created', 'last_updated', 'user',
|
||||
'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'id', 'url', 'display_url', 'display', 'rack', 'units', 'unit_count', 'status', 'created', 'last_updated',
|
||||
'user', 'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'status', 'user', 'description', 'units')
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import django_filters
|
||||
import netaddr
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Func, IntegerField
|
||||
from django.utils.translation import gettext as _
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
@@ -609,11 +610,30 @@ class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||
field_name='units',
|
||||
lookup_expr='contains'
|
||||
)
|
||||
unit_count_min = django_filters.NumberFilter(
|
||||
field_name='unit_count',
|
||||
lookup_expr='gte',
|
||||
label=_('Minimum unit count'),
|
||||
)
|
||||
unit_count_max = django_filters.NumberFilter(
|
||||
field_name='unit_count',
|
||||
lookup_expr='lte',
|
||||
label=_('Maximum unit count'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = ('id', 'created', 'description')
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
# Annotate unit_count here so unit_count_min/unit_count_max filters can reference it.
|
||||
# When called from the list view the queryset is already annotated; Django silently
|
||||
# overwrites a duplicate annotation with the same expression, so this is safe.
|
||||
queryset = queryset.annotate(
|
||||
unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
|
||||
)
|
||||
return super().filter_queryset(queryset)
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
|
||||
@@ -475,7 +475,7 @@ class RackReservationFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('status', 'user_id', name=_('Reservation')),
|
||||
FieldSet('status', 'user_id', 'unit_count_min', 'unit_count_max', name=_('Reservation')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'group_id', 'rack_id', name=_('Rack')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
|
||||
@@ -534,6 +534,14 @@ class RackReservationFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
required=False,
|
||||
label=_('User')
|
||||
)
|
||||
unit_count_min = forms.IntegerField(
|
||||
required=False,
|
||||
label=_("Minimum U's")
|
||||
)
|
||||
unit_count_max = forms.IntegerField(
|
||||
required=False,
|
||||
label=_("Maximum U's")
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
|
||||
@@ -241,6 +241,9 @@ class RackReservationTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
orderable=False,
|
||||
verbose_name=_('Units')
|
||||
)
|
||||
unit_count = tables.Column(
|
||||
verbose_name=_("Total U's")
|
||||
)
|
||||
status = columns.ChoiceFieldColumn(
|
||||
verbose_name=_('Status'),
|
||||
)
|
||||
@@ -251,7 +254,9 @@ class RackReservationTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
model = RackReservation
|
||||
fields = (
|
||||
'pk', 'id', 'reservation', 'site', 'location', 'group', 'rack', 'unit_list', 'status', 'user', 'created',
|
||||
'tenant', 'tenant_group', 'description', 'comments', 'tags', 'actions', 'created', 'last_updated',
|
||||
'pk', 'id', 'reservation', 'site', 'location', 'group', 'rack', 'unit_list', 'unit_count', 'status',
|
||||
'user', 'created', 'tenant', 'tenant_group', 'description', 'comments', 'tags', 'actions', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'reservation', 'site', 'rack', 'unit_list', 'unit_count', 'status', 'user', 'description',
|
||||
)
|
||||
default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'status', 'user', 'description')
|
||||
|
||||
@@ -1205,7 +1205,7 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
reservations = (
|
||||
RackReservation(
|
||||
rack=racks[0],
|
||||
units=[1, 2, 3],
|
||||
units=[1, 2],
|
||||
status=RackReservationStatusChoices.STATUS_ACTIVE,
|
||||
user=users[0],
|
||||
tenant=tenants[0],
|
||||
@@ -1213,7 +1213,7 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
),
|
||||
RackReservation(
|
||||
rack=racks[1],
|
||||
units=[4, 5, 6],
|
||||
units=[1, 2, 3],
|
||||
status=RackReservationStatusChoices.STATUS_PENDING,
|
||||
user=users[1],
|
||||
tenant=tenants[1],
|
||||
@@ -1221,7 +1221,7 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
),
|
||||
RackReservation(
|
||||
rack=racks[2],
|
||||
units=[7, 8, 9],
|
||||
units=[1, 2, 3, 4],
|
||||
status=RackReservationStatusChoices.STATUS_STALE,
|
||||
user=users[2],
|
||||
tenant=tenants[2],
|
||||
@@ -1291,6 +1291,14 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'description': ['foobar1', 'foobar2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_unit_count(self):
|
||||
params = {'unit_count_min': 3}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'unit_count_max': 3}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'unit_count_min': 3, 'unit_count_max': 3}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_tenant_group(self):
|
||||
tenant_groups = TenantGroup.objects.all()[:2]
|
||||
params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
|
||||
|
||||
@@ -70,6 +70,7 @@ class RackRolePanel(panels.OrganizationalObjectPanel):
|
||||
|
||||
class RackReservationPanel(panels.ObjectAttributesPanel):
|
||||
units = attrs.TextAttr('unit_list')
|
||||
unit_count = attrs.TextAttr('unit_count', label=_("Total U's"))
|
||||
status = attrs.ChoiceAttr('status')
|
||||
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||
user = attrs.RelatedObjectAttr('user')
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.contrib import messages
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||
from django.db import router, transaction
|
||||
from django.db.models import Prefetch
|
||||
from django.db.models import Func, IntegerField, Prefetch
|
||||
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
@@ -1227,7 +1227,9 @@ class RackBulkDeleteView(generic.BulkDeleteView):
|
||||
|
||||
@register_model_view(RackReservation, 'list', path='', detail=False)
|
||||
class RackReservationListView(generic.ObjectListView):
|
||||
queryset = RackReservation.objects.all()
|
||||
queryset = RackReservation.objects.annotate(
|
||||
unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
|
||||
)
|
||||
filterset = filtersets.RackReservationFilterSet
|
||||
filterset_form = forms.RackReservationFilterForm
|
||||
table = tables.RackReservationTable
|
||||
@@ -1236,7 +1238,9 @@ class RackReservationListView(generic.ObjectListView):
|
||||
|
||||
@register_model_view(RackReservation)
|
||||
class RackReservationView(generic.ObjectView):
|
||||
queryset = RackReservation.objects.all()
|
||||
queryset = RackReservation.objects.annotate(
|
||||
unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
|
||||
)
|
||||
layout = layout.SimpleLayout(
|
||||
left_panels=[
|
||||
panels.RackPanel(accessor='object.rack', only=['region', 'site', 'location', 'group', 'name']),
|
||||
|
||||
Reference in New Issue
Block a user