diff --git a/docs/data-model/dns.md b/docs/data-model/dns.md
index 3951e9c41..5ac5218f0 100644
--- a/docs/data-model/dns.md
+++ b/docs/data-model/dns.md
@@ -4,8 +4,12 @@ The DNS component of NetBox deals with the management of DNS zones.
A zone corresponds to a zone file in a DNS server, it stores the SOA (Start Of Authority) record and other records that are stored as Record objects.
-As zones are readable through the REST API, it is possible to write some external script which automatically generates zone files for a DNS server,
-this feature is not directly provided by NetBox though.
+The SOA Serial field is automatically created and updated each time something changes in the zone, i.e. each time you edit IP addresses or records
+belonging to the zone, or the zone itself. It's in the following format : YYYYMMDDN with Y the year, M the month, D the day and N a counter.
+
+Every zone can be exported as a zone file in BIND format, directly readable by a DNS server. As zones are readable through the REST API,
+with a field containing their BIND format, it is possible to write an external script which automatically updates a DNS server
+configuration from the Netbox database.
---
diff --git a/netbox/dns/filters.py b/netbox/dns/filters.py
index 0f54af96e..480aa956a 100644
--- a/netbox/dns/filters.py
+++ b/netbox/dns/filters.py
@@ -6,7 +6,7 @@ from .models import (
Zone,
Record,
)
-from .forms import record_type_choices, record_category_choices
+from .forms import record_type_choices
class ZoneFilter(django_filters.FilterSet):
name = django_filters.CharFilter(
@@ -32,11 +32,6 @@ class RecordFilter(django_filters.FilterSet):
label = 'Type',
choices = record_type_choices
)
- cateogry = django_filters.MultipleChoiceFilter(
- name = 'category',
- label = 'Category',
- choices = record_category_choices
- )
name = django_filters.CharFilter(
name = 'name',
lookup_type = 'icontains',
@@ -48,7 +43,7 @@ class RecordFilter(django_filters.FilterSet):
class Meta:
model=Record
- field = ['name', 'record_type', 'value', 'category']
+ field = ['name', 'record_type', 'value']
def filter_name_or_value(self, queryset, value):
if not value:
diff --git a/netbox/dns/forms.py b/netbox/dns/forms.py
index 0115fe4c9..72f8dc7d8 100644
--- a/netbox/dns/forms.py
+++ b/netbox/dns/forms.py
@@ -19,11 +19,10 @@ class ZoneForm(forms.ModelForm, BootstrapMixin):
class Meta:
model=Zone
- fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description']
+ 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_serial': 'SOA Serial',
'soa_refresh': 'SOA Refresh',
'soa_retry': 'SOA Retry',
'soa_expire': 'SOA Expire',
@@ -34,7 +33,6 @@ class ZoneForm(forms.ModelForm, BootstrapMixin):
'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_serial': "Serial string in SOA record (e.g. 2016071401)",
'soa_refresh': "Refresh time, in seconds",
'soa_retry': "Retry time, in seconds",
'soa_expire': "Expire time, in seconds",
@@ -45,7 +43,7 @@ class ZoneFromCSVForm(forms.ModelForm):
class Meta:
model=Zone
- fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'description']
+ 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)
@@ -77,14 +75,13 @@ class RecordForm(forms.ModelForm, BootstrapMixin):
class Meta:
model=Record
- fields = ['name', 'category', 'record_type', 'priority', 'zone', 'address', 'value', 'description']
+ 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)',
- 'category': 'Category (e.g. SLA, Server or Customer)',
'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',
@@ -98,7 +95,15 @@ class RecordFromCSVForm(forms.ModelForm):
class Meta:
model=Record
- fields = ['zone', 'name', 'category', 'record_type', 'priority', 'address', 'value', 'description']
+ 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)
@@ -106,7 +111,6 @@ class RecordImportForm(BulkImportForm, BootstrapMixin):
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')
- category = forms.CharField(max_length=100, required=False, label='Category')
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)
@@ -130,22 +134,9 @@ def record_type_choices():
type_choices[r.record_type]+=1
return [(t, '{} ({})'.format(t, count)) for t,count in type_choices.items()]
-def record_category_choices():
- category_choices = {}
- records = Record.objects.all()
- for r in records:
- if r.category:
- if not r.category in category_choices:
- category_choices[r.category]=1
- else:
- category_choices[r.category]+=1
- return [(c, '{} ({})'.format(c, count)) for c,count in category_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}))
- category = forms.MultipleChoiceField(required=False, choices=record_category_choices, label='Category',
- widget=forms.SelectMultiple(attrs={'size': 8}))
diff --git a/netbox/dns/migrations/0004_auto_20160722_0820.py b/netbox/dns/migrations/0004_auto_20160722_0820.py
new file mode 100644
index 000000000..5087660aa
--- /dev/null
+++ b/netbox/dns/migrations/0004_auto_20160722_0820.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-22 08:20
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dns', '0003_auto_20160721_1059'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='record',
+ options={'ordering': ['name']},
+ ),
+ migrations.RemoveField(
+ model_name='record',
+ name='category',
+ ),
+ migrations.AlterField(
+ model_name='record',
+ name='address',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='records', to='ipam.IPAddress'),
+ ),
+ ]
diff --git a/netbox/dns/models.py b/netbox/dns/models.py
index a5857cabc..4efb3ba90 100644
--- a/netbox/dns/models.py
+++ b/netbox/dns/models.py
@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
-from ipam.models import IPAddress
+#from ipam.models import IPAddress
from utilities.models import CreatedUpdatedModel
import time
@@ -33,6 +33,29 @@ class Zone(CreatedUpdatedModel):
def get_absolute_url(self):
return reverse('dns:zone', args=[self.pk])
+ def save(self, *args, **kwargs):
+ self.update_serial()
+ super(Zone, self).save(*args, **kwargs)
+
+ 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+'1'
+ else:
+ serial_date = self.soa_serial[:8]
+ serial_num = self.soa_serial[8:]
+
+ if serial_date!=current_date:
+ self.soa_serial = current_date+'1'
+ else:
+ serial_num = int(serial_num)
+ serial_num += 1
+ self.soa_serial = current_date + str(serial_num)
+
+
def to_csv(self):
return ','.join([
self.name,
@@ -80,13 +103,12 @@ class Record(CreatedUpdatedModel):
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.SET_NULL, blank=True, null=True)
+ address = models.ForeignKey('ipam.IPAddress', related_name='records', on_delete=models.PROTECT, blank=True, null=True)
value = models.CharField(max_length=100, blank=True)
- category = models.CharField(max_length=20, blank=True)
description = models.CharField(max_length=20, blank=True)
class Meta:
- ordering = ['category']
+ ordering = ['name']
def __unicode__(self):
return self.name
@@ -95,15 +117,23 @@ class Record(CreatedUpdatedModel):
return reverse('dns:record', args=[self.pk])
def clean(self):
- record_type = record_type.upper()
+ 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)
+
+ #POST_DELETE RECEIVER !!!
+ def delete(self, *args, **kwargs):
+ self.zone.save() # in order to update serial.
+ super(Record, self).delete(*args, **kwargs)
def to_csv(self):
return ','.join([
self.zone.name,
self.name,
- self.category,
self.record_type,
str(self.priority) if self.priority else '',
str(self.address) if self.address else '',
diff --git a/netbox/dns/tables.py b/netbox/dns/tables.py
index 2b48706bc..8d0177584 100644
--- a/netbox/dns/tables.py
+++ b/netbox/dns/tables.py
@@ -30,7 +30,6 @@ class ZoneTable(BaseTable):
class RecordTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name')
- category = tables.Column(verbose_name='Category')
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')
@@ -39,16 +38,15 @@ class RecordTable(BaseTable):
class Meta(BaseTable.Meta):
model=Record
- fields = ('pk', 'name', 'category', 'record_type', 'priority', 'address', 'value')
+ fields = ('pk', 'name', 'record_type', 'priority', 'address', 'value')
class RecordBriefTable(BaseTable):
name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name')
- category = tables.Column(verbose_name='Category')
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', 'category', 'record_type', 'priority', 'zone')
+ fields = ('name', 'record_type', 'priority', 'zone')
\ No newline at end of file
diff --git a/netbox/dns/views.py b/netbox/dns/views.py
index c2c55c6c6..2dd35b6ae 100644
--- a/netbox/dns/views.py
+++ b/netbox/dns/views.py
@@ -83,7 +83,10 @@ class ZoneBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
- return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
+ zlist = self.cls.objects.filter(pk__in=pk_list)
+ for z in zlist:
+ z.save()
+ return zlist.update(**fields_to_update)
class ZoneBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -146,7 +149,10 @@ class RecordBulkEditView(PermissionRequiredMixin, BulkEditView):
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
- return self.cls.objects.filter(pk__in=pk_list).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'
diff --git a/netbox/templates/dns/record.html b/netbox/templates/dns/record.html
index 00055d89c..8e7cbdf42 100644
--- a/netbox/templates/dns/record.html
+++ b/netbox/templates/dns/record.html
@@ -56,15 +56,6 @@
Name |
{{ record.name }} |
-
- Category |
-
- {% if record.category %}
- {{ record.category }}
- {% else %}
- -
- {% endif %}
- |
Type |
{{ record.record_type }} |
@@ -130,7 +121,7 @@
- {{ bind_export }} |
+ {{ bind_export }} |
diff --git a/netbox/templates/dns/record_bulk_edit.html b/netbox/templates/dns/record_bulk_edit.html
index e5a389a03..5e4586bfa 100644
--- a/netbox/templates/dns/record_bulk_edit.html
+++ b/netbox/templates/dns/record_bulk_edit.html
@@ -7,7 +7,6 @@
{% for record in selected_objects %}
{{ record.name }} |
- {{ record.category }} |
{{ record.record_type }} |
{{ record.priority }} |
{{ record.address }} |
diff --git a/netbox/templates/dns/record_import.html b/netbox/templates/dns/record_import.html
index 28ca8bc6d..ca428a2e5 100644
--- a/netbox/templates/dns/record_import.html
+++ b/netbox/templates/dns/record_import.html
@@ -38,11 +38,6 @@
Host name, @ for origin |
www |
-
- Category |
- Category (e.g. SLA, Server or Customer ; optional) |
- Server |
-
Type |
Record type |
@@ -71,7 +66,7 @@
Example
- foo.net,www,Server,AAAA,,192.168.1.110/16,,Backend API server
+ foo.net,www,AAAA,,192.168.1.110/16,,Backend API server
{% endblock %}
diff --git a/netbox/templates/dns/zone.html b/netbox/templates/dns/zone.html
index 4b76d5ea8..317278c86 100644
--- a/netbox/templates/dns/zone.html
+++ b/netbox/templates/dns/zone.html
@@ -162,7 +162,7 @@
- {{ bind_export }} |
+ {{ bind_export }} |
diff --git a/netbox/templates/dns/zone_import.html b/netbox/templates/dns/zone_import.html
index 62dd3e8d0..e24a6ebce 100644
--- a/netbox/templates/dns/zone_import.html
+++ b/netbox/templates/dns/zone_import.html
@@ -48,11 +48,6 @@
The responsible party for the domain |
ns.foo.net. noc.foo.net. |
-
- SOA Serial |
- Serial string in SOA record |
- 2016070401 |
-
SOA Refresh |
Refresh time, in seconds |
@@ -81,7 +76,7 @@
Example
- foo.net,10800,@,ns.foo.net. noc.foo.net.,2016070401,3600,3600,604800,1800,Mail servers zone
+ foo.net,10800,@,ns.foo.net. noc.foo.net.,3600,3600,604800,1800,Mail servers zone
{% endblock %}