Sfoglia il codice sorgente

#6732 - Swap ASN M2M to Site model and update some templates/filters

Daniel Sheppard 4 anni fa
parent
commit
7625a2dd3c

+ 1 - 1
netbox/dcim/forms/bulk_edit.py

@@ -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

+ 0 - 11
netbox/dcim/forms/models.py

@@ -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 - 0
netbox/dcim/migrations/0141_asn_model.py

@@ -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'),
+        ),
+    ]

+ 5 - 0
netbox/dcim/models/sites.py

@@ -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
     )
     )

+ 1 - 1
netbox/dcim/tests/test_filtersets.py

@@ -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):

+ 6 - 1
netbox/dcim/views.py

@@ -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,
         }
         }
 
 
 
 

+ 17 - 2
netbox/ipam/forms/models.py

@@ -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 - 0
netbox/ipam/migrations/0052_asn_model.py

@@ -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'],
+            },
+        ),
+    ]

+ 1 - 5
netbox/ipam/models/ip.py

@@ -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()
 
 

+ 2 - 5
netbox/ipam/views.py

@@ -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(
-            list(instance.sites.all()),
-            orderable=False
-        )
+        sites = instance.sites.restrict(request.user, 'view').all()
 
 
         return {
         return {
-            'sites_table': sites_table,
+            'sites': sites,
         }
         }
 
 
 
 

+ 14 - 0
netbox/templates/dcim/site.html

@@ -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>

+ 16 - 3
netbox/templates/ipam/asn.html

@@ -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' %}
-        {% include 'inc/panels/tags.html' with tags=object.tags.all url='ipam:asn_list' %}
+        <div class="card">
+            <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>