mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
#6732 - Swap ASN M2M to Site model and update some templates/filters
This commit is contained in:
parent
330c498fe4
commit
7625a2dd3c
@ -117,7 +117,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEd
|
|||||||
required=False,
|
required=False,
|
||||||
label='ASN'
|
label='ASN'
|
||||||
)
|
)
|
||||||
asns = DynamicModelChoiceField(
|
asns = DynamicModelMultipleChoiceField(
|
||||||
queryset=ASN.objects.all(),
|
queryset=ASN.objects.all(),
|
||||||
label=_('ASNs'),
|
label=_('ASNs'),
|
||||||
required=False
|
required=False
|
||||||
|
@ -172,17 +172,6 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
'longitude': "Longitude in decimal format (xx.yyyyyy)"
|
'longitude': "Longitude in decimal format (xx.yyyyyy)"
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, data=None, instance=None, *args, **kwargs):
|
|
||||||
super().__init__(data=data, instance=instance, *args, **kwargs)
|
|
||||||
|
|
||||||
if self.instance and self.instance.pk is not None:
|
|
||||||
self.fields['asns'].initial = self.instance.asns.all().values_list('id', flat=True)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
instance = super().save(*args, **kwargs)
|
|
||||||
instance.asns.set(self.cleaned_data['asns'])
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class LocationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
class LocationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
|
19
netbox/dcim/migrations/0141_asn_model.py
Normal file
19
netbox/dcim/migrations/0141_asn_model.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-11-02 16:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0052_asn_model'),
|
||||||
|
('dcim', '0140_wireless'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='site',
|
||||||
|
name='asns',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='sites', to='ipam.ASN'),
|
||||||
|
),
|
||||||
|
]
|
@ -195,6 +195,11 @@ class Site(PrimaryModel):
|
|||||||
verbose_name='ASN',
|
verbose_name='ASN',
|
||||||
help_text='32-bit autonomous system number'
|
help_text='32-bit autonomous system number'
|
||||||
)
|
)
|
||||||
|
asns = models.ManyToManyField(
|
||||||
|
to='ipam.ASN',
|
||||||
|
related_name='sites',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
time_zone = TimeZoneField(
|
time_zone = TimeZoneField(
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
@ -183,7 +183,7 @@ class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_asns(self):
|
def test_asns(self):
|
||||||
params = {'asns': [65001, 65002]}
|
params = {'asns': [64512, 65002]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_latitude(self):
|
def test_latitude(self):
|
||||||
|
@ -310,7 +310,6 @@ class SiteView(generic.ObjectView):
|
|||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
stats = {
|
stats = {
|
||||||
'asn_count': ASN.objects.restrict(request.user, 'view').filter(sites=instance).count(),
|
|
||||||
'rack_count': Rack.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
'rack_count': Rack.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'device_count': Device.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
'device_count': Device.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
@ -333,9 +332,15 @@ class SiteView(generic.ObjectView):
|
|||||||
cumulative=True
|
cumulative=True
|
||||||
).restrict(request.user, 'view').filter(site=instance)
|
).restrict(request.user, 'view').filter(site=instance)
|
||||||
|
|
||||||
|
asns = ASN.objects.restrict(request.user, 'view').filter(sites=instance)
|
||||||
|
asn_count = asns.count()
|
||||||
|
|
||||||
|
stats.update({'asn_count': asn_count})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'locations': locations,
|
'locations': locations,
|
||||||
|
'asns': asns,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,14 +134,18 @@ class ASNForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
label='Sites',
|
label='Sites',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
tags = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Tag.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ASN
|
model = ASN
|
||||||
fields = [
|
fields = [
|
||||||
'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description'
|
'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'tags'
|
||||||
]
|
]
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('ASN', ('asn', 'rir', 'sites', 'description')),
|
('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
|
||||||
('Tenancy', ('tenant_group', 'tenant')),
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
@ -152,6 +156,17 @@ class ASNForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
'date_added': DatePicker(),
|
'date_added': DatePicker(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, data=None, instance=None, *args, **kwargs):
|
||||||
|
super().__init__(data=data, instance=instance, *args, **kwargs)
|
||||||
|
|
||||||
|
if self.instance and self.instance.pk is not None:
|
||||||
|
self.fields['sites'].initial = self.instance.sites.all().values_list('id', flat=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
instance = super().save(*args, **kwargs)
|
||||||
|
instance.sites.set(self.cleaned_data['sites'])
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class RoleForm(BootstrapMixin, CustomFieldModelForm):
|
class RoleForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
38
netbox/ipam/migrations/0052_asn_model.py
Normal file
38
netbox/ipam/migrations/0052_asn_model.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-11-02 16:16
|
||||||
|
|
||||||
|
import dcim.fields
|
||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0004_extend_tag_support'),
|
||||||
|
('extras', '0064_configrevision'),
|
||||||
|
('ipam', '0051_extend_tag_support'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ASN',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('asn', dcim.fields.ASNField(unique=True)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('rir', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='asns', to='ipam.rir')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='asns', to='tenancy.tenant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'ASN',
|
||||||
|
'verbose_name_plural': 'ASNs',
|
||||||
|
'ordering': ['asn'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -71,6 +71,7 @@ class RIR(OrganizationalModel):
|
|||||||
return reverse('ipam:rir', args=[self.pk])
|
return reverse('ipam:rir', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||||
class ASN(PrimaryModel):
|
class ASN(PrimaryModel):
|
||||||
|
|
||||||
asn = ASNField(
|
asn = ASNField(
|
||||||
@ -98,11 +99,6 @@ class ASN(PrimaryModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
sites = models.ManyToManyField(
|
|
||||||
to='dcim.Site',
|
|
||||||
related_name='asns',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
@ -214,13 +214,10 @@ class ASNView(generic.ObjectView):
|
|||||||
queryset = ASN.objects.all()
|
queryset = ASN.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
sites_table = SiteTable(
|
sites = instance.sites.restrict(request.user, 'view').all()
|
||||||
list(instance.sites.all()),
|
|
||||||
orderable=False
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'sites_table': sites_table,
|
'sites': sites,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -260,6 +260,20 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">
|
||||||
|
ASNs
|
||||||
|
</h5>
|
||||||
|
<div class='card-body'>
|
||||||
|
{% if asns %}
|
||||||
|
{% for asn in asns %}
|
||||||
|
<a href="{{ asn.get_absolute_url }}"><span class="badge bg-primary">{{ asn }}</span></a>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% include 'inc/panels/image_attachments.html' %}
|
{% include 'inc/panels/image_attachments.html' %}
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,17 +47,30 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% include 'inc/panels/tags.html' with tags=object.tags.all url='ipam:asn_list' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
<div class="card">
|
||||||
{% include 'inc/panels/tags.html' with tags=object.tags.all url='ipam:asn_list' %}
|
<h5 class="card-header">
|
||||||
|
Sites
|
||||||
|
</h5>
|
||||||
|
<div class='card-body'>
|
||||||
|
{% if sites %}
|
||||||
|
{% for site in sites %}
|
||||||
|
<a href="{{ site.get_absolute_url }}"><span class="badge bg-primary">{{ site }}</span></a>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% include 'inc/panel_table.html' with table=sites_table heading='Sites' %}
|
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user