mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* fixed prefix header to represent new serial "vlan_vid" * shows option in creation now * fixed visibility on rack page * cleanup * Added view to Tenant page * Moved migration for update from #1666 and fixed tenant enumeration in FilterForm * Fixed conflict #1 * Fixed filters from merge and made migration merge * added tenant to api * Fixed migrations problem * Added Tenant to bulkedit option
This commit is contained in:
committed by
Jeremy Stretch
parent
db0ef95fe3
commit
fbd39da8ca
@ -218,7 +218,7 @@ class RackReservationSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = ['id', 'rack', 'units', 'created', 'user', 'description']
|
fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
|
||||||
|
|
||||||
|
|
||||||
class WritableRackReservationSerializer(ValidatedModelSerializer):
|
class WritableRackReservationSerializer(ValidatedModelSerializer):
|
||||||
|
@ -208,6 +208,16 @@ class RackReservationFilter(django_filters.FilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Group',
|
label='Group',
|
||||||
)
|
)
|
||||||
|
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
label='Tenant (ID)',
|
||||||
|
)
|
||||||
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='tenant__slug',
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Tenant (slug)',
|
||||||
|
)
|
||||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=User.objects.all(),
|
queryset=User.objects.all(),
|
||||||
label='User (ID)',
|
label='User (ID)',
|
||||||
|
@ -379,13 +379,13 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
# Rack reservations
|
# Rack reservations
|
||||||
#
|
#
|
||||||
|
|
||||||
class RackReservationForm(BootstrapMixin, forms.ModelForm):
|
class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
|
||||||
units = SimpleArrayField(forms.IntegerField(), widget=ArrayFieldSelectMultiple(attrs={'size': 10}))
|
units = SimpleArrayField(forms.IntegerField(), widget=ArrayFieldSelectMultiple(attrs={'size': 10}))
|
||||||
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
|
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = ['units', 'user', 'description']
|
fields = ['units', 'user', 'tenant_group', 'tenant', 'description']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -415,11 +415,17 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
|
|||||||
label='Rack group',
|
label='Rack group',
|
||||||
null_option=(0, 'None')
|
null_option=(0, 'None')
|
||||||
)
|
)
|
||||||
|
tenant = FilterChoiceField(
|
||||||
|
queryset=Tenant.objects.annotate(filter_count=Count('rackreservations')),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_option=(0, 'None')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=RackReservation.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=RackReservation.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'), required=False)
|
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'), required=False)
|
||||||
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -805,10 +811,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
pk = self.instance.pk if self.instance.pk else None
|
pk = self.instance.pk if self.instance.pk else None
|
||||||
try:
|
try:
|
||||||
if self.is_bound and self.data.get('rack') and str(self.data.get('face')):
|
if self.is_bound and self.data.get('rack') and str(self.data.get('face')):
|
||||||
position_choices = Rack.objects.get(pk=self.data['rack'])\
|
position_choices = Rack.objects.get(pk=self.data['rack']) \
|
||||||
.get_rack_units(face=self.data.get('face'), exclude=pk)
|
.get_rack_units(face=self.data.get('face'), exclude=pk)
|
||||||
elif self.initial.get('rack') and str(self.initial.get('face')):
|
elif self.initial.get('rack') and str(self.initial.get('face')):
|
||||||
position_choices = Rack.objects.get(pk=self.initial['rack'])\
|
position_choices = Rack.objects.get(pk=self.initial['rack']) \
|
||||||
.get_rack_units(face=self.initial.get('face'), exclude=pk)
|
.get_rack_units(face=self.initial.get('face'), exclude=pk)
|
||||||
else:
|
else:
|
||||||
position_choices = []
|
position_choices = []
|
||||||
|
22
netbox/dcim/migrations/0050_rackreservation_tenant.py
Normal file
22
netbox/dcim/migrations/0050_rackreservation_tenant.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2017-10-30 20:43
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0003_unicode_literals'),
|
||||||
|
('dcim', '0049_rackreservation_change_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rackreservation',
|
||||||
|
name='tenant',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='rackreservations', to='tenancy.Tenant'),
|
||||||
|
),
|
||||||
|
]
|
@ -417,6 +417,7 @@ class RackReservation(models.Model):
|
|||||||
rack = models.ForeignKey('Rack', related_name='reservations', on_delete=models.CASCADE)
|
rack = models.ForeignKey('Rack', related_name='reservations', on_delete=models.CASCADE)
|
||||||
units = ArrayField(models.PositiveSmallIntegerField())
|
units = ArrayField(models.PositiveSmallIntegerField())
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='rackreservations', on_delete=models.PROTECT)
|
||||||
user = models.ForeignKey(User, on_delete=models.PROTECT)
|
user = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||||
description = models.CharField(max_length=100)
|
description = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
@ -244,6 +244,7 @@ class RackImportTable(BaseTable):
|
|||||||
|
|
||||||
class RackReservationTable(BaseTable):
|
class RackReservationTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
||||||
unit_list = tables.Column(orderable=False, verbose_name='Units')
|
unit_list = tables.Column(orderable=False, verbose_name='Units')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
@ -252,7 +253,7 @@ class RackReservationTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = ('pk', 'rack', 'unit_list', 'user', 'created', 'description', 'actions')
|
fields = ('pk', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -326,7 +326,7 @@ class RackView(View):
|
|||||||
|
|
||||||
rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
|
rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
|
||||||
|
|
||||||
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True)\
|
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True) \
|
||||||
.select_related('device_type__manufacturer')
|
.select_related('device_type__manufacturer')
|
||||||
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
||||||
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
|
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
|
||||||
@ -1783,7 +1783,7 @@ class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ConsoleConnectionsListView(ObjectListView):
|
class ConsoleConnectionsListView(ObjectListView):
|
||||||
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\
|
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False) \
|
||||||
.order_by('cs_port__device__name', 'cs_port__name')
|
.order_by('cs_port__device__name', 'cs_port__name')
|
||||||
filter = filters.ConsoleConnectionFilter
|
filter = filters.ConsoleConnectionFilter
|
||||||
filter_form = forms.ConsoleConnectionFilterForm
|
filter_form = forms.ConsoleConnectionFilterForm
|
||||||
@ -1792,7 +1792,7 @@ class ConsoleConnectionsListView(ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class PowerConnectionsListView(ObjectListView):
|
class PowerConnectionsListView(ObjectListView):
|
||||||
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\
|
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False) \
|
||||||
.order_by('power_outlet__device__name', 'power_outlet__name')
|
.order_by('power_outlet__device__name', 'power_outlet__name')
|
||||||
filter = filters.PowerConnectionFilter
|
filter = filters.PowerConnectionFilter
|
||||||
filter_form = forms.PowerConnectionFilterForm
|
filter_form = forms.PowerConnectionFilterForm
|
||||||
@ -1801,7 +1801,7 @@ class PowerConnectionsListView(ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionsListView(ObjectListView):
|
class InterfaceConnectionsListView(ObjectListView):
|
||||||
queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\
|
queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device') \
|
||||||
.order_by('interface_a__device__name', 'interface_a__name')
|
.order_by('interface_a__device__name', 'interface_a__name')
|
||||||
filter = filters.InterfaceConnectionFilter
|
filter = filters.InterfaceConnectionFilter
|
||||||
filter_form = forms.InterfaceConnectionFilterForm
|
filter_form = forms.InterfaceConnectionFilterForm
|
||||||
|
@ -233,12 +233,14 @@
|
|||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Units</th>
|
<th>Units</th>
|
||||||
|
<th>Tenant</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for resv in reservations %}
|
{% for resv in reservations %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ resv.unit_list }}</td>
|
<td>{{ resv.unit_list }}</td>
|
||||||
|
<td>{{ resv.tenant }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ resv.description }}<br />
|
{{ resv.description }}<br />
|
||||||
<small>{{ resv.user }} · {{ resv.created }}</small>
|
<small>{{ resv.user }} · {{ resv.created }}</small>
|
||||||
|
@ -100,6 +100,10 @@
|
|||||||
<h2><a href="{% url 'dcim:rack_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.rack_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.rack_count }}</a></h2>
|
<h2><a href="{% url 'dcim:rack_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.rack_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.rack_count }}</a></h2>
|
||||||
<p>Racks</p>
|
<p>Racks</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<h2><a href="{% url 'dcim:rackreservation_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.rackreservation_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.rackreservation_count }}</a></h2>
|
||||||
|
<p>Rack Reservations</p>
|
||||||
|
</div>
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'dcim:device_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.device_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
|
<h2><a href="{% url 'dcim:device_list' %}?tenant={{ tenant.slug }}" class="btn {% if stats.device_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
|
||||||
<p>Devices</p>
|
<p>Devices</p>
|
||||||
|
@ -7,7 +7,7 @@ from django.urls import reverse
|
|||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from dcim.models import Site, Rack, Device
|
from dcim.models import Site, Rack, Device, RackReservation
|
||||||
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
@ -75,6 +75,7 @@ class TenantView(View):
|
|||||||
stats = {
|
stats = {
|
||||||
'site_count': Site.objects.filter(tenant=tenant).count(),
|
'site_count': Site.objects.filter(tenant=tenant).count(),
|
||||||
'rack_count': Rack.objects.filter(tenant=tenant).count(),
|
'rack_count': Rack.objects.filter(tenant=tenant).count(),
|
||||||
|
'rackreservation_count': RackReservation.objects.filter(tenant=tenant).count(),
|
||||||
'device_count': Device.objects.filter(tenant=tenant).count(),
|
'device_count': Device.objects.filter(tenant=tenant).count(),
|
||||||
'vrf_count': VRF.objects.filter(tenant=tenant).count(),
|
'vrf_count': VRF.objects.filter(tenant=tenant).count(),
|
||||||
'prefix_count': Prefix.objects.filter(
|
'prefix_count': Prefix.objects.filter(
|
||||||
|
Reference in New Issue
Block a user