From f873848e2950fd9c5bb5bc165d3e5b6d88935cba Mon Sep 17 00:00:00 2001 From: rdujardin Date: Wed, 3 Aug 2016 12:51:24 +0200 Subject: [PATCH] Fix to pass PEP8 check and tests --- netbox/dns/admin.py | 13 +- netbox/dns/api/serializers.py | 28 +-- netbox/dns/api/urls.py | 18 +- netbox/dns/api/views.py | 71 ++++---- netbox/dns/apps.py | 2 +- netbox/dns/filters.py | 78 ++++---- netbox/dns/formfields.py | 44 +++-- netbox/dns/forms.py | 185 +++++++++---------- netbox/dns/models.py | 322 ++++++++++++++++----------------- netbox/dns/tables.py | 80 ++++---- netbox/dns/urls.py | 42 ++--- netbox/dns/views.py | 262 ++++++++++++++------------- netbox/ipam/fixtures/ipam.json | 8 +- netbox/ipam/forms.py | 2 - netbox/ipam/models.py | 113 ++++++------ 15 files changed, 650 insertions(+), 618 deletions(-) diff --git a/netbox/dns/admin.py b/netbox/dns/admin.py index d5956c624..afcca50b2 100644 --- a/netbox/dns/admin.py +++ b/netbox/dns/admin.py @@ -1,17 +1,18 @@ from django.contrib import admin from .models import ( - Zone, Record, + Zone, Record, ) @admin.register(Zone) class ZoneAdmin(admin.ModelAdmin): - list_display = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial'] - prepopulated_fields = { - 'soa_name': ['name'], - } + list_display = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial'] + prepopulated_fields = { + 'soa_name': ['name'], + } + @admin.register(Record) class RecordAdmin(admin.ModelAdmin): - list_display = ['name', 'zone', 'record_type', 'priority', 'address', 'value'] + list_display = ['name', 'zone', 'record_type', 'priority', 'address', 'value'] diff --git a/netbox/dns/api/serializers.py b/netbox/dns/api/serializers.py index ba7286a1c..504827301 100644 --- a/netbox/dns/api/serializers.py +++ b/netbox/dns/api/serializers.py @@ -7,32 +7,36 @@ from dns.models import Zone, Record # Zones # + class ZoneSerializer(serializers.ModelSerializer): - class Meta: - model=Zone - fields = ['id', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description'] + class Meta: + model = Zone + fields = ['id', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description'] + class ZoneNestedSerializer(ZoneSerializer): - class Meta(ZoneSerializer.Meta): - fields = ['id', 'name'] + class Meta(ZoneSerializer.Meta): + fields = ['id', 'name'] # # Records # + class RecordSerializer(serializers.ModelSerializer): - zone = ZoneNestedSerializer() - address = IPAddressNestedSerializer() + zone = ZoneNestedSerializer() + address = IPAddressNestedSerializer() + + class Meta: + model = Record + fields = ['id', 'name', 'record_type', 'priority', 'zone', 'address', 'value', 'description'] - class Meta: - model=Record - fields = ['id', 'name', 'record_type', 'priority', 'zone', 'address', 'value', 'description'] class RecordNestedSerializer(RecordSerializer): - class Meta(RecordSerializer.Meta): - fields = ['id', 'name', 'record_type', 'zone'] + class Meta(RecordSerializer.Meta): + fields = ['id', 'name', 'record_type', 'zone'] diff --git a/netbox/dns/api/urls.py b/netbox/dns/api/urls.py index 75a9c184d..701be0efa 100644 --- a/netbox/dns/api/urls.py +++ b/netbox/dns/api/urls.py @@ -4,16 +4,16 @@ from .views import * urlpatterns = [ - # Zones - url(r'^zones/$', ZoneListView.as_view(), name='zone_list'), - url(r'^zones/(?P\d+)/$', ZoneDetailView.as_view(), name='zone_detail'), + # Zones + url(r'^zones/$', ZoneListView.as_view(), name='zone_list'), + url(r'^zones/(?P\d+)/$', ZoneDetailView.as_view(), name='zone_detail'), - # Records - url(r'^records/$', RecordListView.as_view(), name='record_list'), - url(r'^records/(?P\d+)/$', RecordDetailView.as_view(), name='record_detail'), + # Records + url(r'^records/$', RecordListView.as_view(), name='record_list'), + url(r'^records/(?P\d+)/$', RecordDetailView.as_view(), name='record_detail'), - # BIND Exports - url(r'^bind/forward/$', bind_forward, name='bind_forward'), - url(r'^bind/reverse/$', bind_reverse, name='bind_reverse'), + # BIND Exports + url(r'^bind/forward/$', bind_forward, name='bind_forward'), + url(r'^bind/reverse/$', bind_reverse, name='bind_reverse'), ] diff --git a/netbox/dns/api/views.py b/netbox/dns/api/views.py index 480e57729..222f46c28 100644 --- a/netbox/dns/api/views.py +++ b/netbox/dns/api/views.py @@ -14,56 +14,63 @@ from . import serializers # Zones # + class ZoneListView(generics.ListAPIView): - """ - List all zones - """ - queryset = Zone.objects.all() - serializer_class = serializers.ZoneSerializer - filter_class = filters.ZoneFilter + """ + List all zones + """ + queryset = Zone.objects.all() + serializer_class = serializers.ZoneSerializer + filter_class = filters.ZoneFilter + class ZoneDetailView(generics.RetrieveAPIView): - """ - Retrieve a single zone - """ - queryset = Zone.objects.all() - serializer_class = serializers.ZoneSerializer + """ + Retrieve a single zone + """ + queryset = Zone.objects.all() + serializer_class = serializers.ZoneSerializer + # # Records # + class RecordListView(generics.ListAPIView): - """ - List all records - """ - queryset = Record.objects.all() - serializer_class = serializers.RecordSerializer + """ + List all records + """ + queryset = Record.objects.all() + serializer_class = serializers.RecordSerializer + class RecordDetailView(generics.RetrieveAPIView): - """ - Retrieve a single record - """ - queryset = Record.objects.all() - serializer_class = serializers.RecordSerializer + """ + Retrieve a single record + """ + queryset = Record.objects.all() + serializer_class = serializers.RecordSerializer + # # BIND Exports # + @api_view(['GET']) def bind_forward(request): - """ - Full export of forward zones in BIND format - """ - zones_list = export_bind_forward() - return Response(zones_list) + """ + Full export of forward zones in BIND format + """ + zones_list = export_bind_forward() + return Response(zones_list) + @api_view(['GET']) def bind_reverse(request): - """ - Full export of reverse zones in BIND format - """ - zones_list = export_bind_reverse() - return Response(zones_list) - + """ + Full export of reverse zones in BIND format + """ + zones_list = export_bind_reverse() + return Response(zones_list) diff --git a/netbox/dns/apps.py b/netbox/dns/apps.py index a8e0badcb..451ca92b8 100644 --- a/netbox/dns/apps.py +++ b/netbox/dns/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class DNSConfig(AppConfig): name = 'dns' - verbose_name='DNS' + verbose_name = 'DNS' diff --git a/netbox/dns/filters.py b/netbox/dns/filters.py index 1c0c73389..8bc26d323 100644 --- a/netbox/dns/filters.py +++ b/netbox/dns/filters.py @@ -3,49 +3,51 @@ from django.db.models import Q from ipam.models import IPAddress from .models import ( - Zone, - Record, + Zone, + Record, ) from .forms import record_type_choices -class ZoneFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - name = 'name', - lookup_type = 'icontains', - label = 'Name', - ) - class Meta: - model = Zone - fields = ['name'] +class ZoneFilter(django_filters.FilterSet): + name = django_filters.CharFilter( + name='name', + lookup_type='icontains', + label='Name', + ) + + class Meta: + model = Zone + fields = ['name'] + class RecordFilter(django_filters.FilterSet): - zone__name = django_filters.ModelMultipleChoiceFilter( - name = 'zone__name', - to_field_name = 'name', - lookup_type = 'icontains', - queryset = Zone.objects.all(), - label = 'Zone (Name)', - ) - record_type = django_filters.MultipleChoiceFilter( - name = 'record_type', - label = 'Type', - choices = record_type_choices - ) - name = django_filters.CharFilter( - name = 'name', - lookup_type = 'icontains', - label = 'Name', - ) - name_or_value_or_ip = django_filters.MethodFilter( - name = 'name_or_value_or_ip', - ) + zone__name = django_filters.ModelMultipleChoiceFilter( + name='zone__name', + to_field_name='name', + lookup_type='icontains', + queryset=Zone.objects.all(), + label='Zone (Name)', + ) + record_type = django_filters.MultipleChoiceFilter( + name='record_type', + label='Type', + choices=record_type_choices + ) + name = django_filters.CharFilter( + name='name', + lookup_type='icontains', + label='Name', + ) + name_or_value_or_ip = django_filters.MethodFilter( + name='name_or_value_or_ip', + ) - class Meta: - model=Record - field = ['name', 'record_type', 'value'] + class Meta: + model = Record + field = ['name', 'record_type', 'value'] - def filter_name_or_value_or_ip(self, queryset, value): - if not value: - return queryset - return queryset.filter(Q(name__icontains=value) | Q(value__icontains=value) | Q(address__address__icontains=value)) + def filter_name_or_value_or_ip(self, queryset, value): + if not value: + return queryset + return queryset.filter(Q(name__icontains=value) | Q(value__icontains=value) | Q(address__address__icontains=value)) diff --git a/netbox/dns/formfields.py b/netbox/dns/formfields.py index 6c1049f65..202363dbb 100644 --- a/netbox/dns/formfields.py +++ b/netbox/dns/formfields.py @@ -12,30 +12,28 @@ from ipam.models import IPAddress, Prefix # class AddressFormField(forms.Field): - default_error_messages = { - 'invalid': "Enter a valid IPv4 or IPv6 address (with CIDR mask).", - } + default_error_messages = { + 'invalid': "Enter a valid IPv4 or IPv6 address (with CIDR mask).", + } - def to_python(self, value): - if not value: - return None + def to_python(self, value): + if not value: + return None - # Ensure that a subnet mask has been specified. This prevents IPs from defaulting to a /32 or /128. - if len(value.split('/')) != 2: - raise ValidationError('CIDR mask (e.g. /24) is required.') - - try: - net = IPNetwork(value) - except AddrFormatError: - raise ValidationError("Please specify a valid IPv4 or IPv6 address.") - - ip = IPAddress.objects.filter(address=value) - if not ip: - net = IPNetwork(value) - obj = IPAddress(address=net) - obj.save() - return obj - else: - return ip[0] + # Ensure that a subnet mask has been specified. This prevents IPs from defaulting to a /32 or /128. + if len(value.split('/')) != 2: + raise ValidationError('CIDR mask (e.g. /24) is required.') + try: + net = IPNetwork(value) + except AddrFormatError: + raise ValidationError("Please specify a valid IPv4 or IPv6 address.") + ip = IPAddress.objects.filter(address=value) + if not ip: + net = IPNetwork(value) + obj = IPAddress(address=net) + obj.save() + return obj + else: + return ip[0] diff --git a/netbox/dns/forms.py b/netbox/dns/forms.py index 94f3d3ffe..06ee42562 100644 --- a/netbox/dns/forms.py +++ b/netbox/dns/forms.py @@ -3,12 +3,12 @@ from django.db.models import Count from ipam.models import IPAddress from utilities.forms import ( - BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm, + BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm, ) from .models import ( - Zone, - Record, + Zone, + Record, ) from .formfields import AddressFormField @@ -17,131 +17,134 @@ from .formfields import AddressFormField # Zones # + class ZoneForm(forms.ModelForm, BootstrapMixin): - class Meta: - model=Zone - fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description'] - labels = { - 'soa_name': 'SOA Name', - 'soa_contact': 'SOA Contact', - 'soa_refresh': 'SOA Refresh', - 'soa_retry': 'SOA Retry', - 'soa_expire': 'SOA Expire', - 'soa_minimum': 'SOA Minimum', - 'description': 'Description', - } - help_texts = { - 'ttl': "Time to live, in seconds", - 'soa_name': "The primary name server for the domain, @ for origin", - 'soa_contact': "The responsible party for the domain (e.g. ns.foo.net. noc.foo.net.)", - 'soa_refresh': "Refresh time, in seconds", - 'soa_retry': "Retry time, in seconds", - 'soa_expire': "Expire time, in seconds", - 'soa_minimum': "Negative result TTL, in seconds", - } + class Meta: + model = Zone + fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description'] + labels = { + 'soa_name': 'SOA Name', + 'soa_contact': 'SOA Contact', + 'soa_refresh': 'SOA Refresh', + 'soa_retry': 'SOA Retry', + 'soa_expire': 'SOA Expire', + 'soa_minimum': 'SOA Minimum', + 'description': 'Description', + } + help_texts = { + 'ttl': "Time to live, in seconds", + 'soa_name': "The primary name server for the domain, @ for origin", + 'soa_contact': "The responsible party for the domain (e.g. ns.foo.net. noc.foo.net.)", + 'soa_refresh': "Refresh time, in seconds", + 'soa_retry': "Retry time, in seconds", + 'soa_expire': "Expire time, in seconds", + 'soa_minimum': "Negative result TTL, in seconds", + } + class ZoneFromCSVForm(forms.ModelForm): - class Meta: - model=Zone - fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description'] + class Meta: + model = Zone + fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description'] + class ZoneImportForm(BulkImportForm, BootstrapMixin): - csv = CSVDataField(csv_form=ZoneFromCSVForm) + csv = CSVDataField(csv_form=ZoneFromCSVForm) + class ZoneBulkEditForm(forms.Form, BootstrapMixin): - pk = forms.ModelMultipleChoiceField(queryset=Zone.objects.all(), widget=forms.MultipleHiddenInput) - name = forms.CharField(max_length=100, required=False, label='Name') - ttl = forms.IntegerField(required=False, label='TTL') - soa_name = forms.CharField(max_length=100, required=False, label='SOA Name') - soa_contact = forms.CharField(max_length=100, required=False, label='SOA Contact') - soa_refresh = forms.IntegerField(required=False, label='SOA Refresh') - soa_retry = forms.IntegerField(required=False, label='SOA Retry') - soa_expire = forms.IntegerField(required=False, label='SOA Expire') - soa_minimum = forms.IntegerField(required=False, label='SOA Minimum') - description = forms.CharField(max_length=100, required=False, label='Description') + pk = forms.ModelMultipleChoiceField(queryset=Zone.objects.all(), widget=forms.MultipleHiddenInput) + name = forms.CharField(max_length=100, required=False, label='Name') + ttl = forms.IntegerField(required=False, label='TTL') + soa_name = forms.CharField(max_length=100, required=False, label='SOA Name') + soa_contact = forms.CharField(max_length=100, required=False, label='SOA Contact') + soa_refresh = forms.IntegerField(required=False, label='SOA Refresh') + soa_retry = forms.IntegerField(required=False, label='SOA Retry') + soa_expire = forms.IntegerField(required=False, label='SOA Expire') + soa_minimum = forms.IntegerField(required=False, label='SOA Minimum') + description = forms.CharField(max_length=100, required=False, label='Description') class ZoneBulkDeleteForm(ConfirmationForm): - pk = forms.ModelMultipleChoiceField(queryset=Zone.objects.all(), widget=forms.MultipleHiddenInput) + pk = forms.ModelMultipleChoiceField(queryset=Zone.objects.all(), widget=forms.MultipleHiddenInput) + class ZoneFilterForm(forms.Form, BootstrapMixin): - pass + pass # # Records # + class RecordForm(forms.ModelForm, BootstrapMixin): - address = AddressFormField(required=False) + address = AddressFormField(required=False) - class Meta: - model=Record - fields = ['name', 'record_type', 'priority', 'zone', 'address', 'value', 'description'] - labels = { - 'record_type': 'Type', - } - help_texts = { - 'name': 'Host name, @ for origin (e.g. www)', - 'record_type': 'Record type (e.g. MX or AAAA)', - 'priority': 'Priority level (e.g. 10)', - 'zone': 'Zone the record belongs to', - 'address': 'IP address if value is an IP address, in AAAA records for instance', - 'value': 'Text value else, in CNAME records for instance' - } + class Meta: + model = Record + fields = ['name', 'record_type', 'priority', 'zone', 'address', 'value', 'description'] + labels = { + 'record_type': 'Type', + } + help_texts = { + 'name': 'Host name, @ for origin (e.g. www)', + 'record_type': 'Record type (e.g. MX or AAAA)', + 'priority': 'Priority level (e.g. 10)', + 'zone': 'Zone the record belongs to', + 'address': 'IP address if value is an IP address, in AAAA records for instance', + 'value': 'Text value else, in CNAME records for instance' + } class RecordFromCSVForm(forms.ModelForm): - zone = forms.ModelChoiceField(queryset=Zone.objects.all(), to_field_name='name', error_messages={'invalid_choice': 'Zone not found.'}) - address = AddressFormField(required=False) + zone = forms.ModelChoiceField(queryset=Zone.objects.all(), to_field_name='name', error_messages={'invalid_choice': 'Zone not found.'}) + address = AddressFormField(required=False) - class Meta: - model=Record - fields = ['zone', 'name', 'record_type', 'priority', 'address', 'value', 'description'] + class Meta: + model = Record + fields = ['zone', 'name', 'record_type', 'priority', 'address', 'value', 'description'] -# class RecordBINDImportForm(forms.Form, BootstrapMixin): -# bind = BINDDataField() -# zone_name = CharField(max_length=100, label='Zone') -# slash_v4 = IntegerField() - -# def clean(self): -# self.cleaned_data class RecordImportForm(BulkImportForm, BootstrapMixin): - csv = CSVDataField(csv_form=RecordFromCSVForm) + csv = CSVDataField(csv_form=RecordFromCSVForm) + class RecordBulkEditForm(forms.Form, BootstrapMixin): - pk = forms.ModelMultipleChoiceField(queryset=Record.objects.all(), widget=forms.MultipleHiddenInput) - name = forms.CharField(max_length=100, required=False, label='Name') - record_type = forms.CharField(max_length=100, required=False, label='Type') - priority = forms.IntegerField(required=False) - zone = forms.ModelChoiceField(queryset=Zone.objects.all(), required=False) - address = AddressFormField(required=False) - value = forms.CharField(max_length=100, required=False) + pk = forms.ModelMultipleChoiceField(queryset=Record.objects.all(), widget=forms.MultipleHiddenInput) + name = forms.CharField(max_length=100, required=False, label='Name') + record_type = forms.CharField(max_length=100, required=False, label='Type') + priority = forms.IntegerField(required=False) + zone = forms.ModelChoiceField(queryset=Zone.objects.all(), required=False) + address = AddressFormField(required=False) + value = forms.CharField(max_length=100, required=False) + class RecordBulkDeleteForm(ConfirmationForm): - pk = forms.ModelMultipleChoiceField(queryset=Record.objects.all(), widget=forms.MultipleHiddenInput) + pk = forms.ModelMultipleChoiceField(queryset=Record.objects.all(), widget=forms.MultipleHiddenInput) + def record_zone_choices(): - zone_choices = Zone.objects.annotate(record_count=Count('records')) - return [(z.name, '{} ({})'.format(z.name, z.record_count)) for z in zone_choices] + zone_choices = Zone.objects.annotate(record_count=Count('records')) + return [(z.name, '{} ({})'.format(z.name, z.record_count)) for z in zone_choices] + def record_type_choices(): - type_choices = {} - records = Record.objects.all() - for r in records: - if not r.record_type in type_choices: - type_choices[r.record_type]=1 - else: - type_choices[r.record_type]+=1 - return [(t, '{} ({})'.format(t, count)) for t,count in type_choices.items()] + type_choices = {} + records = Record.objects.all() + for r in records: + if r.record_type not in type_choices: + type_choices[r.record_type] = 1 + else: + type_choices[r.record_type] += 1 + return [(t, '{} ({})'.format(t, count)) for t, count in type_choices.items()] + class RecordFilterForm(forms.Form, BootstrapMixin): - zone__name = forms.MultipleChoiceField(required=False, choices=record_zone_choices, label='Zone', - widget=forms.SelectMultiple(attrs={'size': 8})) - record_type = forms.MultipleChoiceField(required=False, choices=record_type_choices, label='Type', - widget=forms.SelectMultiple(attrs={'size': 8})) - + zone__name = forms.MultipleChoiceField(required=False, choices=record_zone_choices, label='Zone', + widget=forms.SelectMultiple(attrs={'size': 8})) + record_type = forms.MultipleChoiceField(required=False, choices=record_type_choices, label='Type', + widget=forms.SelectMultiple(attrs={'size': 8})) diff --git a/netbox/dns/models.py b/netbox/dns/models.py index d4b73003b..61fd417f7 100644 --- a/netbox/dns/models.py +++ b/netbox/dns/models.py @@ -4,7 +4,6 @@ from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models -#from ipam.models import IPAddress from utilities.models import CreatedUpdatedModel import time @@ -14,201 +13,202 @@ from django.dispatch import receiver import ipam.models + class Zone(CreatedUpdatedModel): - """ - A Zone represents a DNS zone. It contains SOA data but no records, records are represented as Record objects. - """ - name = models.CharField(max_length=100) - ttl = models.PositiveIntegerField() - soa_name = models.CharField(max_length=100) - soa_contact = models.CharField(max_length=100) + """ + A Zone represents a DNS zone. It contains SOA data but no records, records are represented as Record objects. + """ + name = models.CharField(max_length=100) + ttl = models.PositiveIntegerField() + soa_name = models.CharField(max_length=100) + soa_contact = models.CharField(max_length=100) - soa_serial = models.CharField(max_length=10) - bind_changed = models.BooleanField(default=True) + soa_serial = models.CharField(max_length=10) + bind_changed = models.BooleanField(default=True) - soa_refresh = models.PositiveIntegerField() - soa_retry = models.PositiveIntegerField() - soa_expire = models.PositiveIntegerField() - soa_minimum = models.PositiveIntegerField() - description = models.CharField(max_length=100, blank=True) + soa_refresh = models.PositiveIntegerField() + soa_retry = models.PositiveIntegerField() + soa_expire = models.PositiveIntegerField() + soa_minimum = models.PositiveIntegerField() + description = models.CharField(max_length=100, blank=True) - class Meta: - ordering = ['name'] + class Meta: + ordering = ['name'] - def __unicode__(self): - return self.name + def __unicode__(self): + return self.name - def get_absolute_url(self): - return reverse('dns:zone', args=[self.pk]) + def get_absolute_url(self): + return reverse('dns:zone', args=[self.pk]) - def save(self, *args, **kwargs): - self.bind_changed = True - super(Zone, self).save(*args, **kwargs) + def save(self, *args, **kwargs): + self.bind_changed = True + super(Zone, self).save(*args, **kwargs) - def set_bind_changed(self, value): - self.bind_changed = value - super(Zone, self).save() + def set_bind_changed(self, value): + self.bind_changed = value + super(Zone, self).save() - def update_serial(self): - """ - Each time a record or the zone is modified, the serial is incremented. - """ - current_date = time.strftime('%Y%m%d',time.localtime()) - if not self.soa_serial: - self.soa_serial = current_date+'01' - else: - serial_date = self.soa_serial[:8] - serial_num = self.soa_serial[8:] - - if serial_date!=current_date: - self.soa_serial = current_date+'01' - else: - serial_num = int(serial_num) - serial_num += 1 - if serial_num<10: - self.soa_serial = current_date + '0' + str(serial_num) - else: - self.soa_serial = current_date + str(serial_num) - self.set_bind_changed(False) + def update_serial(self): + """ + Each time a record or the zone is modified, the serial is incremented. + """ + current_date = time.strftime('%Y%m%d', time.localtime()) + if not self.soa_serial: + self.soa_serial = current_date + '01' + else: + serial_date = self.soa_serial[:8] + serial_num = self.soa_serial[8:] + if serial_date != current_date: + self.soa_serial = current_date + '01' + else: + serial_num = int(serial_num) + serial_num += 1 + if serial_num < 10: + self.soa_serial = current_date + '0' + str(serial_num) + else: + self.soa_serial = current_date + str(serial_num) + self.set_bind_changed(False) - def to_csv(self): - return ','.join([ - self.name, - str(self.ttl), - self.soa_name, - self.soa_contact, - self.soa_serial, - str(self.soa_refresh), - str(self.soa_retry), - str(self.soa_expire), - str(self.soa_minimum), - self.description, - ]) - - def to_bind(self,records): - if self.bind_changed: - self.update_serial() - bind_records = '' - for r in records: - bind_records += r.to_bind()+'\n' - bind_export = '\n'.join([ - '; '+self.name+((' ('+self.description+')') if self.description else ''), - '; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) ', - '', - '$TTL '+str(self.ttl), - self.soa_name.ljust(30)+' IN '+'SOA '+self.soa_contact+' (', - ' '+self.soa_serial.ljust(30)+' ; serial', - ' '+str(self.soa_refresh).ljust(30)+' ; refresh', - ' '+str(self.soa_retry).ljust(30)+' ; retry', - ' '+str(self.soa_expire).ljust(30)+' ; expire', - ' '+str(self.soa_minimum).ljust(29)+') ; minimum', - '', - '', - '', - ]) - bind_export += '\n'+bind_records - bind_export += '\n'+'; end ' - return bind_export + def to_csv(self): + return ','.join([ + self.name, + str(self.ttl), + self.soa_name, + self.soa_contact, + self.soa_serial, + str(self.soa_refresh), + str(self.soa_retry), + str(self.soa_expire), + str(self.soa_minimum), + self.description, + ]) + def to_bind(self, records): + if self.bind_changed: + self.update_serial() + bind_records = '' + for r in records: + bind_records += r.to_bind() + '\n' + bind_export = '\n'.join([ + '; ' + self.name + ((' (' + self.description + ')') if self.description else ''), + '; gen by netbox ( ' + time.strftime('%A %B %d %Y %H:%M:%S', time.localtime()) + ' ) ', + '', + '$TTL ' + str(self.ttl), + self.soa_name.ljust(30) + ' IN ' + 'SOA ' + self.soa_contact + ' (', + ' ' + self.soa_serial.ljust(30) + ' ; serial', + ' ' + str(self.soa_refresh).ljust(30) + ' ; refresh', + ' ' + str(self.soa_retry).ljust(30) + ' ; retry', + ' ' + str(self.soa_expire).ljust(30) + ' ; expire', + ' ' + str(self.soa_minimum).ljust(29) + ') ; minimum', + '', + '', + '', + ]) + bind_export += '\n' + bind_records + bind_export += '\n' + '; end ' + return bind_export class Record(CreatedUpdatedModel): - """ - A Record represents a DNS record, i.e. a row in a DNS zone. - """ - name = models.CharField(max_length=100) - record_type = models.CharField(max_length=10) - priority = models.PositiveIntegerField(blank=True, null=True) - zone = models.ForeignKey('Zone', related_name='records', on_delete=models.CASCADE) - address = models.ForeignKey('ipam.IPAddress', related_name='records', on_delete=models.PROTECT, blank=True, null=True) - value = models.CharField(max_length=100, blank=True) - description = models.CharField(max_length=20, blank=True) + """ + A Record represents a DNS record, i.e. a row in a DNS zone. + """ + name = models.CharField(max_length=100) + record_type = models.CharField(max_length=10) + priority = models.PositiveIntegerField(blank=True, null=True) + zone = models.ForeignKey('Zone', related_name='records', on_delete=models.CASCADE) + address = models.ForeignKey('ipam.IPAddress', related_name='records', on_delete=models.PROTECT, blank=True, null=True) + value = models.CharField(max_length=100, blank=True) + description = models.CharField(max_length=20, blank=True) - class Meta: - ordering = ['name'] + class Meta: + ordering = ['name'] - def __unicode__(self): - return self.name + def __unicode__(self): + return self.name - def get_absolute_url(self): - return reverse('dns:record', args=[self.pk]) + def get_absolute_url(self): + return reverse('dns:record', args=[self.pk]) - def clean(self): - self.record_type = self.record_type.upper() - if not self.address and not self.value: - raise ValidationError("DNS records must have either an IP address or a text value") - - def save(self, *args, **kwargs): - self.zone.save() # in order to update serial. - super(Record, self).save(*args, **kwargs) + def clean(self): + self.record_type = self.record_type.upper() + if not self.address and not self.value: + raise ValidationError("DNS records must have either an IP address or a text value") - def to_csv(self): - return ','.join([ - self.zone.name, - self.name, - self.record_type, - str(self.priority) if self.priority else '', - str(self.address) if self.address else '', - self.value, - self.description, - ]) + def save(self, *args, **kwargs): + self.zone.save() # in order to update serial. + super(Record, self).save(*args, **kwargs) + + def to_csv(self): + return ','.join([ + self.zone.name, + self.name, + self.record_type, + str(self.priority) if self.priority else '', + str(self.address) if self.address else '', + self.value, + self.description, + ]) + + def to_bind(self): + return ''.join([ + (self.name if self.name != '@' else '').ljust(30), + ' IN ', + self.record_type.upper().ljust(10), + ' ', + (str(self.priority) if self.priority else '').ljust(4), + ' ', + (str(self.address).split('/')[0] if self.address else self.value).ljust(25), + ' ', + ' ; ' + self.description + ' ; gen by netbox ( ' + time.strftime('%A %B %d %Y %H:%M:%S', time.localtime()) + ' ) ' + ]) - def to_bind(self): - return ''.join([ - (self.name if self.name!='@' else '').ljust(30), - ' IN ', - self.record_type.upper().ljust(10), - ' ', - (str(self.priority) if self.priority else '').ljust(4), - ' ', - (str(self.address).split('/')[0] if self.address else self.value).ljust(25), - ' ', - ' ; '+self.description+' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) ' - ]) @receiver(pre_delete, sender=Record) def on_record_delete(sender, **kwargs): - kwargs['instance'].zone.save() + kwargs['instance'].zone.save() + # # BIND Exports # + def export_bind_forward(): - zones = Zone.objects.all() + zones = Zone.objects.all() - zones_list = [] - for z in zones: - records = Record.objects.filter(zone=z) - zones_list.append({ - 'num': len(zones_list), - 'id': z.name, - 'content': z.to_bind(records) - }) + zones_list = [] + for z in zones: + records = Record.objects.filter(zone=z) + zones_list.append({ + 'num': len(zones_list), + 'id': z.name, + 'content': z.to_bind(records) + }) + + return zones_list - return zones_list def export_bind_reverse(): - zones = {} + zones = {} - prefixes = ipam.models.Prefix.objects.all() + prefixes = ipam.models.Prefix.objects.all() - for p in prefixes: - child_ip = ipam.models.IPAddress.objects.filter(address__net_contained_or_equal=str(p.prefix)) - z = p.to_bind(child_ip) - for zz in z: - if not zz['id'] in zones: - zones[zz['id']] = zz['content'] - - zones_list = [] - for zid,zc in zones.items(): - zones_list.append({ - 'num': len(zones_list), - 'id': zid, - 'content': zc, - }) - - return zones_list + for p in prefixes: + child_ip = ipam.models.IPAddress.objects.filter(address__net_contained_or_equal=str(p.prefix)) + z = p.to_bind(child_ip) + for zz in z: + if not zz['id'] in zones: + zones[zz['id']] = zz['content'] + zones_list = [] + for zid, zc in zones.items(): + zones_list.append({ + 'num': len(zones_list), + 'id': zid, + 'content': zc, + }) + return zones_list diff --git a/netbox/dns/tables.py b/netbox/dns/tables.py index 3444bc84c..01f092359 100644 --- a/netbox/dns/tables.py +++ b/netbox/dns/tables.py @@ -10,54 +10,58 @@ from .models import Zone, Record # Zones # -class ZoneTable(BaseTable): - pk = ToggleColumn() - name = tables.LinkColumn('dns:zone', args=[Accessor('pk')], verbose_name='Name') - record_count = tables.Column(verbose_name='Records') - ttl = tables.Column(verbose_name='TTL') - soa_name = tables.Column(verbose_name='SOA Name') - soa_contact = tables.Column(verbose_name='SOA Contact') - soa_serial = tables.Column(verbose_name='SOA Serial') - class Meta(BaseTable.Meta): - model = Zone - fields = ('pk', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial') +class ZoneTable(BaseTable): + pk = ToggleColumn() + name = tables.LinkColumn('dns:zone', args=[Accessor('pk')], verbose_name='Name') + record_count = tables.Column(verbose_name='Records') + ttl = tables.Column(verbose_name='TTL') + soa_name = tables.Column(verbose_name='SOA Name') + soa_contact = tables.Column(verbose_name='SOA Contact') + soa_serial = tables.Column(verbose_name='SOA Serial') + + class Meta(BaseTable.Meta): + model = Zone + fields = ('pk', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial') + # # Records # -class RecordTable(BaseTable): - pk = ToggleColumn() - name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name') - record_type = tables.Column(verbose_name='Type') - priority = tables.Column(verbose_name='Priority') - address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('address.pk')], verbose_name='IP Address') - value = tables.Column(verbose_name='Text Value') - zone = tables.LinkColumn('dns:zone', args=[Accessor('zone.pk')], verbose_name='Zone') - class Meta(BaseTable.Meta): - model=Record - fields = ('pk', 'name', 'record_type', 'priority', 'address', 'value') +class RecordTable(BaseTable): + pk = ToggleColumn() + name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name') + record_type = tables.Column(verbose_name='Type') + priority = tables.Column(verbose_name='Priority') + address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('address.pk')], verbose_name='IP Address') + value = tables.Column(verbose_name='Text Value') + zone = tables.LinkColumn('dns:zone', args=[Accessor('zone.pk')], verbose_name='Zone') + + class Meta(BaseTable.Meta): + model = Record + fields = ('pk', 'name', 'record_type', 'priority', 'address', 'value') + class RecordBriefTable(BaseTable): - name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name') - record_type = tables.Column(verbose_name='Type') - priority = tables.Column(verbose_name='Priority') - zone = tables.LinkColumn('dns:zone', args=[Accessor('zone.pk')], verbose_name='Zone') + name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name') + record_type = tables.Column(verbose_name='Type') + priority = tables.Column(verbose_name='Priority') + zone = tables.LinkColumn('dns:zone', args=[Accessor('zone.pk')], verbose_name='Zone') + + class Meta(BaseTable.Meta): + model = Record + fields = ('name', 'record_type', 'priority', 'zone') - class Meta(BaseTable.Meta): - model=Record - fields = ('name', 'record_type', 'priority', 'zone') class RecordZoneTable(BaseTable): - name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name') - record_type = tables.Column(verbose_name='Type') - priority = tables.Column(verbose_name='Priority') - address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('address.pk')], verbose_name='IP Address') - value = tables.Column(verbose_name='Value') + name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name') + record_type = tables.Column(verbose_name='Type') + priority = tables.Column(verbose_name='Priority') + address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('address.pk')], verbose_name='IP Address') + value = tables.Column(verbose_name='Value') - class Meta(BaseTable.Meta): - model=Record - fields = ('name', 'record_type', 'priority', 'address', 'value') - \ No newline at end of file + class Meta(BaseTable.Meta): + model = Record + fields = ('name', 'record_type', 'priority', 'address', 'value') diff --git a/netbox/dns/urls.py b/netbox/dns/urls.py index 98e38fcc8..1fd231f5c 100644 --- a/netbox/dns/urls.py +++ b/netbox/dns/urls.py @@ -4,28 +4,28 @@ from . import views urlpatterns = [ - # Zones - url(r'^zones/$', views.ZoneListView.as_view(), name='zone_list'), - url(r'^zones/add/$', views.ZoneEditView.as_view(), name='zone_add'), - url(r'^zones/import/$', views.ZoneBulkImportView.as_view(), name='zone_import'), - url(r'^zones/edit/$', views.ZoneBulkEditView.as_view(), name='zone_bulk_edit'), - url(r'^zones/delete/$', views.ZoneBulkDeleteView.as_view(), name='zone_bulk_delete'), - url(r'^zones/(?P\d+)/$', views.zone, name='zone'), - url(r'^zones/(?P\d+)/edit/$', views.ZoneEditView.as_view(), name='zone_edit'), - url(r'^zones/(?P\d+)/delete/$', views.ZoneDeleteView.as_view(), name='zone_delete'), + # Zones + url(r'^zones/$', views.ZoneListView.as_view(), name='zone_list'), + url(r'^zones/add/$', views.ZoneEditView.as_view(), name='zone_add'), + url(r'^zones/import/$', views.ZoneBulkImportView.as_view(), name='zone_import'), + url(r'^zones/edit/$', views.ZoneBulkEditView.as_view(), name='zone_bulk_edit'), + url(r'^zones/delete/$', views.ZoneBulkDeleteView.as_view(), name='zone_bulk_delete'), + url(r'^zones/(?P\d+)/$', views.zone, name='zone'), + url(r'^zones/(?P\d+)/edit/$', views.ZoneEditView.as_view(), name='zone_edit'), + url(r'^zones/(?P\d+)/delete/$', views.ZoneDeleteView.as_view(), name='zone_delete'), - # Records - url(r'^records/$', views.RecordListView.as_view(), name='record_list'), - url(r'^records/add/$', views.RecordEditView.as_view(), name='record_add'), - url(r'^records/import/$', views.RecordBulkImportView.as_view(), name='record_import'), - url(r'^records/edit/$', views.RecordBulkEditView.as_view(), name='record_bulk_edit'), - url(r'^records/delete/$', views.RecordBulkDeleteView.as_view(), name='record_bulk_delete'), - url(r'^records/(?P\d+)/$', views.record, name='record'), - url(r'^records/(?P\d+)/edit/$', views.RecordEditView.as_view(), name='record_edit'), - url(r'^records/(?P\d+)/delete/$', views.RecordDeleteView.as_view(), name='record_delete'), + # Records + url(r'^records/$', views.RecordListView.as_view(), name='record_list'), + url(r'^records/add/$', views.RecordEditView.as_view(), name='record_add'), + url(r'^records/import/$', views.RecordBulkImportView.as_view(), name='record_import'), + url(r'^records/edit/$', views.RecordBulkEditView.as_view(), name='record_bulk_edit'), + url(r'^records/delete/$', views.RecordBulkDeleteView.as_view(), name='record_bulk_delete'), + url(r'^records/(?P\d+)/$', views.record, name='record'), + url(r'^records/(?P\d+)/edit/$', views.RecordEditView.as_view(), name='record_edit'), + url(r'^records/(?P\d+)/delete/$', views.RecordDeleteView.as_view(), name='record_delete'), - # BIND Exports - url(r'^bind/forward/$', views.full_forward, name='full_forward'), - url(r'^bind/reverse/$', views.full_reverse, name='full_reverse'), + # BIND Exports + url(r'^bind/forward/$', views.full_forward, name='full_forward'), + url(r'^bind/reverse/$', views.full_reverse, name='full_reverse'), ] diff --git a/netbox/dns/views.py b/netbox/dns/views.py index b8f3ad0d9..7e3ec1492 100644 --- a/netbox/dns/views.py +++ b/netbox/dns/views.py @@ -8,81 +8,88 @@ from django.http import HttpResponse from ipam.models import IPAddress, Prefix from utilities.paginator import EnhancedPaginator from utilities.views import ( - BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, + BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ) from . import filters, forms, tables from .models import Zone, Record, export_bind_forward, export_bind_reverse from .tables import RecordZoneTable -import StringIO, zipfile, time +import StringIO +import zipfile +import time # # Zones # + class ZoneListView(ObjectListView): - queryset = Zone.objects.annotate(record_count=Count('records')) - filter = filters.ZoneFilter - filter_form = forms.ZoneFilterForm - table = tables.ZoneTable - edit_permissions = ['dns.change_zone', 'dns.delete_zone'] - template_name = 'dns/zone_list.html' + queryset = Zone.objects.annotate(record_count=Count('records')) + filter = filters.ZoneFilter + filter_form = forms.ZoneFilterForm + table = tables.ZoneTable + edit_permissions = ['dns.change_zone', 'dns.delete_zone'] + template_name = 'dns/zone_list.html' + def zone(request, pk): - zone = get_object_or_404(Zone.objects.all(), pk=pk) - records = Record.objects.filter(zone=zone) - record_count = len(records) + zone = get_object_or_404(Zone.objects.all(), pk=pk) + records = Record.objects.filter(zone=zone) + record_count = len(records) - # DNS records - dns_records = Record.objects.filter(zone=zone) - dns_records_table = RecordZoneTable(dns_records) + # DNS records + dns_records = Record.objects.filter(zone=zone) + dns_records_table = RecordZoneTable(dns_records) + + return render(request, 'dns/zone.html', { + 'zone': zone, + 'records': records, + 'record_count': record_count, + 'dns_records_table': dns_records_table, + }) - return render(request, 'dns/zone.html', { - 'zone': zone, - 'records': records, - 'record_count': record_count, - 'dns_records_table': dns_records_table, - }) class ZoneEditView(PermissionRequiredMixin, ObjectEditView): - permission_required = 'dns.change_zone' - model = Zone - form_class = forms.ZoneForm - cancel_url = 'dns:zone_list' + permission_required = 'dns.change_zone' + model = Zone + form_class = forms.ZoneForm + cancel_url = 'dns:zone_list' + class ZoneDeleteView(PermissionRequiredMixin, ObjectDeleteView): - permission_required = 'dns.delete_zone' - model = Zone - redirect_url = 'dns:zone_list' + permission_required = 'dns.delete_zone' + model = Zone + redirect_url = 'dns:zone_list' class ZoneBulkImportView(PermissionRequiredMixin, BulkImportView): - permission_required = 'dns.add_zone' - form = forms.ZoneImportForm - table = tables.ZoneTable - template_name = 'dns/zone_import.html' - obj_list_url = 'dns:zone_list' + permission_required = 'dns.add_zone' + form = forms.ZoneImportForm + table = tables.ZoneTable + template_name = 'dns/zone_import.html' + obj_list_url = 'dns:zone_list' + class ZoneBulkEditView(PermissionRequiredMixin, BulkEditView): - permission_required = 'dns.change_zone' - cls = Zone - form = forms.ZoneBulkEditForm - template_name = 'dns/zone_bulk_edit.html' - default_redirect_url = 'dns:zone_list' + permission_required = 'dns.change_zone' + cls = Zone + form = forms.ZoneBulkEditForm + template_name = 'dns/zone_bulk_edit.html' + default_redirect_url = 'dns:zone_list' - def update_objects(self, pk_list, form): + def update_objects(self, pk_list, form): - fields_to_update = {} - for field in ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum']: - if form.cleaned_data[field]: - fields_to_update[field] = form.cleaned_data[field] + fields_to_update = {} + for field in ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum']: + if form.cleaned_data[field]: + fields_to_update[field] = form.cleaned_data[field] - zlist = self.cls.objects.filter(pk__in=pk_list) - for z in zlist: - z.set_bind_changed(True) - return zlist.update(**fields_to_update) + zlist = self.cls.objects.filter(pk__in=pk_list) + for z in zlist: + z.set_bind_changed(True) + return zlist.update(**fields_to_update) class ZoneBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): @@ -91,111 +98,122 @@ class ZoneBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): form = forms.ZoneBulkDeleteForm default_redirect_url = 'dns:zone_list' + # # Records # + class RecordListView(ObjectListView): - queryset = Record.objects.all() - filter = filters.RecordFilter - filter_form = forms.RecordFilterForm - table = tables.RecordTable - edit_permissions = ['dns.change_record', 'dns.delete_record'] - template_name = 'dns/record_list.html' + queryset = Record.objects.all() + filter = filters.RecordFilter + filter_form = forms.RecordFilterForm + table = tables.RecordTable + edit_permissions = ['dns.change_record', 'dns.delete_record'] + template_name = 'dns/record_list.html' + def record(request, pk): - record = get_object_or_404(Record.objects.all(), pk=pk) - bind_export = record.to_bind() + record = get_object_or_404(Record.objects.all(), pk=pk) + bind_export = record.to_bind() + + return render(request, 'dns/record.html', { + 'record': record, + 'bind_export': bind_export, + }) - return render(request, 'dns/record.html', { - 'record': record, - 'bind_export': bind_export, - }) class RecordEditView(PermissionRequiredMixin, ObjectEditView): - permission_required = 'dns.change_record' - model = Record - form_class = forms.RecordForm - cancel_url = 'dns:record_list' + permission_required = 'dns.change_record' + model = Record + form_class = forms.RecordForm + cancel_url = 'dns:record_list' + class RecordDeleteView(PermissionRequiredMixin, ObjectDeleteView): - permission_required = 'dns.delete_record' - model = Record - redirect_url = 'dns:record_list' + permission_required = 'dns.delete_record' + model = Record + redirect_url = 'dns:record_list' + class RecordBulkImportView(PermissionRequiredMixin, BulkImportView): - permission_required = 'dns.add_record' - form = forms.RecordImportForm - table = tables.RecordTable - template_name = 'dns/record_import.html' - obj_list_url = 'dns:record_list' + permission_required = 'dns.add_record' + form = forms.RecordImportForm + table = tables.RecordTable + template_name = 'dns/record_import.html' + obj_list_url = 'dns:record_list' + class RecordBulkEditView(PermissionRequiredMixin, BulkEditView): - permission_required = 'dns.change_record' - cls = Record - form = forms.RecordBulkEditForm - template_name = 'dns/record_bulk_edit.html' - default_redirect_url = 'dns:record_list' + permission_required = 'dns.change_record' + cls = Record + form = forms.RecordBulkEditForm + template_name = 'dns/record_bulk_edit.html' + default_redirect_url = 'dns:record_list' - def update_objects(self, pk_list, form): + def update_objects(self, pk_list, form): - fields_to_update = {} - for field in ['name', 'record_type', 'priority', 'zone', 'address', 'value']: - if form.cleaned_data[field]: - fields_to_update[field] = form.cleaned_data[field] + fields_to_update = {} + for field in ['name', 'record_type', 'priority', 'zone', 'address', 'value']: + if form.cleaned_data[field]: + fields_to_update[field] = form.cleaned_data[field] + + rlist = self.cls.objects.filter(pk__in=pk_list) + if rlist: + rlist[0].save() + return rlist.update(**fields_to_update) - rlist = self.cls.objects.filter(pk__in=pk_list) - if rlist: - rlist[0].save() - return rlist.update(**fields_to_update) class RecordBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): - permission_required = 'dns.delete_record' - cls = Record - form = forms.RecordBulkEditForm - default_redirect_url = 'dns:record_list' + permission_required = 'dns.delete_record' + cls = Record + form = forms.RecordBulkEditForm + default_redirect_url = 'dns:record_list' + # # BIND Exports # -def bind_export(request, zones_list, context): - download = request.GET.get('download') - if download: - if download == 'all': - zbuf = StringIO.StringIO() - zfile = zipfile.ZipFile(zbuf, mode='w') - temp = [] - for z in zones_list: - temp.append(StringIO.StringIO()) - temp[len(temp)-1].write(z['content']) - zfile.writestr(z['id'],str(temp[len(temp)-1].getvalue())) - zfile.close() - response = HttpResponse( - zbuf.getvalue(), - content_type = 'application/zip' - ) - response['Content-Disposition'] = 'attachment; filename="netbox_dns_{}_{}.zip"'.format(context, str(int(time.time()))) - return response - else: - response = HttpResponse( - zones_list[int(download)]['content'], - content_type='text/plain' - ) - response['Content-Disposition'] = 'attachment; filename="{}"'.format(zones_list[int(download)]['id']) - return response - else: - return render(request, 'dns/bind_export.html', { - 'context': context[0].upper() + context[1:], - 'zones': zones_list, - 'bind_export_count': len(zones_list), - }) +def bind_export(request, zones_list, context): + download = request.GET.get('download') + if download: + if download == 'all': + zbuf = StringIO.StringIO() + zfile = zipfile.ZipFile(zbuf, mode='w') + temp = [] + for z in zones_list: + temp.append(StringIO.StringIO()) + temp[len(temp) - 1].write(z['content']) + zfile.writestr(z['id'], str(temp[len(temp) - 1].getvalue())) + zfile.close() + response = HttpResponse( + zbuf.getvalue(), + content_type='application/zip' + ) + response['Content-Disposition'] = 'attachment; filename="netbox_dns_{}_{}.zip"'.format(context, str(int(time.time()))) + return response + else: + response = HttpResponse( + zones_list[int(download)]['content'], + content_type='text/plain' + ) + response['Content-Disposition'] = 'attachment; filename="{}"'.format(zones_list[int(download)]['id']) + return response + + else: + return render(request, 'dns/bind_export.html', { + 'context': context[0].upper() + context[1:], + 'zones': zones_list, + 'bind_export_count': len(zones_list), + }) + def full_forward(request): - return bind_export(request, export_bind_forward(), 'forward') + return bind_export(request, export_bind_forward(), 'forward') + def full_reverse(request): - return bind_export(request, export_bind_reverse(), 'reverse') - + return bind_export(request, export_bind_reverse(), 'reverse') diff --git a/netbox/ipam/fixtures/ipam.json b/netbox/ipam/fixtures/ipam.json index db92ee9c9..36b5d6299 100644 --- a/netbox/ipam/fixtures/ipam.json +++ b/netbox/ipam/fixtures/ipam.json @@ -71,10 +71,10 @@ "soa_name": "", "soa_contact": "", "soa_serial": "", - "soa_refresh": "", - "soa_retry": "", - "soa_expire": "", - "soa_minimum": "" + "soa_refresh": null, + "soa_retry": null, + "soa_expire": null, + "soa_minimum": null } }, { diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index af67a5553..5c70ff80c 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -411,7 +411,6 @@ class IPAddressFromCSVForm(forms.ModelForm): model = IPAddress fields = ['address', 'vrf', 'tenant', 'ptr', 'device', 'interface_name', 'is_primary', 'description'] - def clean(self): device = self.cleaned_data.get('device') @@ -461,7 +460,6 @@ class IPAddressBulkEditForm(forms.Form, BootstrapMixin): description = forms.CharField(max_length=100, required=False) - def ipaddress_family_choices(): return [('', 'All'), (4, 'IPv4'), (6, 'IPv6')] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index d146fbc2a..ffcceb0f9 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -13,7 +13,8 @@ import dns.models from .fields import IPNetworkField, IPAddressField -import time, ipaddress +import time +import ipaddress import netaddr @@ -245,7 +246,7 @@ class Prefix(CreatedUpdatedModel): objects = PrefixQuerySet.as_manager() - #Reverse DNS + # Reverse DNS ttl = models.PositiveIntegerField(blank=True, null=True) soa_name = models.CharField(max_length=100, blank=True) soa_contact = models.CharField(max_length=100, blank=True) @@ -296,15 +297,15 @@ class Prefix(CreatedUpdatedModel): """ Each time a record or the zone is modified, the serial is incremented. """ - current_date = time.strftime('%Y%m%d',time.localtime()) + current_date = time.strftime('%Y%m%d', time.localtime()) if not self.soa_serial: - self.soa_serial = current_date+'01' + self.soa_serial = current_date + '01' else: serial_date = self.soa_serial[:8] serial_num = self.soa_serial[8:] - - if serial_date!=current_date: - self.soa_serial = current_date+'01' + + if serial_date != current_date: + self.soa_serial = current_date + '01' else: serial_num = int(serial_num) serial_num += 1 @@ -346,51 +347,51 @@ class Prefix(CreatedUpdatedModel): def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] - def to_bind(self,ipaddresses): + def to_bind(self, ipaddresses): if self.bind_changed: self.update_serial() zones = {} - def header (zone_id): return '\n'.join([ - '; '+zone_id, - '; gen from prefix '+str(self.prefix)+' ('+(self.description if self.description else '')+') by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) ', - '', - '$TTL '+str(self.ttl), - self.soa_name.ljust(30)+' IN '+'SOA '+self.soa_contact+' (', - ' '+self.soa_serial.ljust(30)+' ; serial', - ' '+str(self.soa_refresh).ljust(30)+' ; refresh', - ' '+str(self.soa_retry).ljust(30)+' ; retry', - ' '+str(self.soa_expire).ljust(30)+' ; expire', - ' '+str(self.soa_minimum).ljust(29)+') ; minimum', - '', - '', - '', - '$ORIGIN '+zone_id, - '', - '', - ]) + def header(zone_id): + return '\n'.join([ + '; ' + zone_id, + '; gen from prefix ' + str(self.prefix) + ' (' + (self.description if self.description else '') + ') by netbox ( ' + time.strftime('%A %B %d %Y %H:%M:%S', time.localtime()) + ' ) ', + '', + '$TTL ' + str(self.ttl), + self.soa_name.ljust(30) + ' IN ' + 'SOA ' + self.soa_contact + ' (', + ' ' + self.soa_serial.ljust(30) + ' ; serial', + ' ' + str(self.soa_refresh).ljust(30) + ' ; refresh', + ' ' + str(self.soa_retry).ljust(30) + ' ; retry', + ' ' + str(self.soa_expire).ljust(30) + ' ; expire', + ' ' + str(self.soa_minimum).ljust(29) + ') ; minimum', + '', + '', + '', + '$ORIGIN ' + zone_id, + '', + '', + ]) if self.prefix.version == 4: pbytes = str(self.prefix).split('/')[0].split('.') pslash = int(str(self.prefix).split('/')[1]) if pslash > 16: - pbytes[3]='0' + pbytes[3] = '0' zslash = 24 - largerPrefix = Prefix.objects.filter(family=4, prefix__net_contains_or_equals=pbytes[0]+'.'+pbytes[1]+'.0.0/16') + largerPrefix = Prefix.objects.filter(family=4, prefix__net_contains_or_equals=pbytes[0] + '.' + pbytes[1] + '.0.0/16') if largerPrefix: - pbytes[2]='0' + pbytes[2] = '0' zslash = 16 else: - pbytes[2]='0' + pbytes[2] = '0' zslash = 16 - if pslash > zslash: pslash = zslash - p = IPNetwork(unicode('.'.join(pbytes)+'/'+str(pslash))) + p = IPNetwork(unicode('.'.join(pbytes) + '/' + str(pslash))) ipaddresses = IPAddress.objects.filter(family=4) for ip in ipaddresses: @@ -402,57 +403,53 @@ class Prefix(CreatedUpdatedModel): if i in p: if zslash == 24: zone_id = ibytes[2] + '.' + ibytes[1] + '.' + ibytes[0] + '.in-addr.arpa.' - if not zone_id in zones: + if zone_id not in zones: zones[zone_id] = header(zone_id) - zones[zone_id] += ibytes[3].ljust(3) + ' IN PTR ' + ip.ptr.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) \n' + zones[zone_id] += ibytes[3].ljust(3) + ' IN PTR ' + ip.ptr.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( ' + time.strftime('%A %B %d %Y %H:%M:%S', time.localtime()) + ' ) \n' else: - zone_id = ibytes[1]+'.'+ibytes[0]+'.in-addr.arpa.' - if not zone_id in zones: + zone_id = ibytes[1] + '.' + ibytes[0] + '.in-addr.arpa.' + if zone_id not in zones: zones[zone_id] = header(zone_id) - zones[zone_id] += (ibytes[3]+'.'+ibytes[2]).ljust(7) + ' IN PTR ' + ip.ptr.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) \n' - - + zones[zone_id] += (ibytes[3] + '.' + ibytes[2]).ljust(7) + ' IN PTR ' + ip.ptr.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( ' + time.strftime('%A %B %d %Y %H:%M:%S', time.localtime()) + ' ) \n' else: pfull = str(ipaddress.IPv6Address(unicode(str(self.prefix).split('/')[0])).exploded) pnibbles = pfull.split(':') - pdigits = pfull.replace(':','') + pdigits = pfull.replace(':', '') pslash = int(str(self.prefix).split('/')[1]) - zslash = pslash if pslash % 16 == 0 else pslash/16+16 - pnibbles = pnibbles[:zslash/16] + ['0000'] * (8 - zslash/16) + zslash = pslash if pslash % 16 == 0 else pslash / 16 + 16 + pnibbles = pnibbles[:zslash / 16] + ['0000'] * (8 - zslash / 16) - largerPrefix = Prefix.objects.filter(family=6, prefix__net_contains_or_equals=':'.join(pnibbles)+'/'+str(zslash)) + largerPrefix = Prefix.objects.filter(family=6, prefix__net_contains_or_equals=':'.join(pnibbles) + '/' + str(zslash)) if largerPrefix: - #choper le plus grand minSlash = 128 for pp in largerPrefix: ppslash = int(str(pp.prefix).split('/')[1]) if ppslash < minSlash: minSlash = ppslash zslash = minSlash - pnibbles = pnibbles[:zslash/16] + ['0000'] * (8 - zslash/16) + pnibbles = pnibbles[:zslash / 16] + ['0000'] * (8 - zslash / 16) for ip in ipaddresses: if ip.ptr: ifull = str(ipaddress.IPv6Address(unicode(str(ip.address).split('/')[0])).exploded) inibbles = ifull.split(':') - idigits = ifull.replace(':','')[::-1] + idigits = ifull.replace(':', '')[::-1] islash = int(str(ip.address).split('/')[1]) - pdigitszone = pdigits[:zslash/4][::-1] - zone_id = '.'.join(pdigitszone)+'.ip6.arpa.' - if not zone_id in zones: + pdigitszone = pdigits[:zslash / 4][::-1] + zone_id = '.'.join(pdigitszone) + '.ip6.arpa.' + if zone_id not in zones: zones[zone_id] = header(zone_id) - zones[zone_id] += ('.'.join(idigits[:32-zslash/4])).ljust(30)+' IN PTR ' + ip.ptr.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) \n' - + zones[zone_id] += ('.'.join(idigits[:32 - zslash / 4])).ljust(30) + ' IN PTR ' + ip.ptr.ljust(40) + ' ; ' + ip.description.ljust(20) + ' ; gen by netbox ( ' + time.strftime('%A %B %d %Y %H:%M:%S', time.localtime()) + ' ) \n' for z in zones: z += '\n\n; end ' ret = [] - for zid,zc in zones.items(): + for zid, zc in zones.items(): ret.append({ 'num': len(ret), 'id': zid, @@ -536,14 +533,14 @@ class IPAddress(CreatedUpdatedModel): record_name = self.ptr[:-len(zone_name)] if record_name.endswith('.'): record_name = record_name[:-1] - record_type = 'A' if self.family==4 else 'AAAA' + record_type = 'A' if self.family == 4 else 'AAAA' dns.models.Record.objects.get_or_create( - name = record_name, - record_type = record_type, - zone = which_zone, - address = self - ) + name=record_name, + record_type=record_type, + zone=which_zone, + address=self + ) def to_csv(self):