mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 10:58:37 -06:00
In DNS : change search/filters & add bind exports
This commit is contained in:
parent
2d3998497e
commit
4046d697fc
@ -1,10 +1,12 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from .models import (
|
from .models import (
|
||||||
Zone,
|
Zone,
|
||||||
Record,
|
Record,
|
||||||
)
|
)
|
||||||
|
from .forms import record_type_choices
|
||||||
|
|
||||||
class ZoneFilter(django_filters.FilterSet):
|
class ZoneFilter(django_filters.FilterSet):
|
||||||
name = django_filters.CharFilter(
|
name = django_filters.CharFilter(
|
||||||
@ -23,14 +25,27 @@ class RecordFilter(django_filters.FilterSet):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
lookup_type='icontains',
|
lookup_type='icontains',
|
||||||
queryset=Zone.objects.all(),
|
queryset=Zone.objects.all(),
|
||||||
label='Zone (name)',
|
label='Zone (Name)',
|
||||||
)
|
)
|
||||||
|
record_type = django_filters.MultipleChoiceFilter(
|
||||||
|
name='record_type',
|
||||||
|
label='Type',
|
||||||
|
choices=record_type_choices
|
||||||
|
)
|
||||||
name = django_filters.CharFilter(
|
name = django_filters.CharFilter(
|
||||||
name='name',
|
name='name',
|
||||||
lookup_type='icontains',
|
lookup_type='icontains',
|
||||||
label='Name',
|
label='Name',
|
||||||
)
|
)
|
||||||
|
name_or_value = django_filters.MethodFilter(
|
||||||
|
name='name_or_value',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model=Record
|
model=Record
|
||||||
field = ['name','record_type','value']
|
field = ['name','record_type','value']
|
||||||
|
|
||||||
|
def filter_name_or_value(self, queryset, value):
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(Q(name__icontains=value) | Q(value__icontains=value))
|
||||||
|
@ -116,12 +116,19 @@ def record_zone_choices():
|
|||||||
zone_choices = Zone.objects.annotate(record_count=Count('records'))
|
zone_choices = Zone.objects.annotate(record_count=Count('records'))
|
||||||
return [(z.name, '{} ({})'.format(z.name, z.record_count)) for z in zone_choices]
|
return [(z.name, '{} ({})'.format(z.name, z.record_count)) for z in zone_choices]
|
||||||
|
|
||||||
#def record_name_choices():
|
def record_type_choices():
|
||||||
#name_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()]
|
||||||
|
|
||||||
class RecordFilterForm(forms.Form, BootstrapMixin):
|
class RecordFilterForm(forms.Form, BootstrapMixin):
|
||||||
zone__name = forms.MultipleChoiceField(required=False, choices=record_zone_choices, label='Zone',
|
zone__name = forms.MultipleChoiceField(required=False, choices=record_zone_choices, label='Zone',
|
||||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
#name = forms.MultipleChoiceField(required=False, choices=record_name_choices, label='Name', widget=forms.SelectMultiple(attrs={'size': 8}))
|
record_type = forms.MultipleChoiceField(required=False, choices=record_type_choices, label='Type',
|
||||||
record_type = forms.CharField(max_length=100, required=False, label='Type')
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ from django.db import models
|
|||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from utilities.models import CreatedUpdatedModel
|
from utilities.models import CreatedUpdatedModel
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
class Zone(CreatedUpdatedModel):
|
class Zone(CreatedUpdatedModel):
|
||||||
"""
|
"""
|
||||||
A Zone represents a DNS zone. It contains SOA data but no records, records are represented as Record objects.
|
A Zone represents a DNS zone. It contains SOA data but no records, records are represented as Record objects.
|
||||||
@ -43,6 +45,28 @@ class Zone(CreatedUpdatedModel):
|
|||||||
str(self.soa_minimum),
|
str(self.soa_minimum),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def to_bind(self,records):
|
||||||
|
bind_records = ''
|
||||||
|
for r in records:
|
||||||
|
bind_records += r.to_bind()+'\n'
|
||||||
|
bind_export = '\n'.join([
|
||||||
|
'; 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):
|
class Record(CreatedUpdatedModel):
|
||||||
@ -66,6 +90,7 @@ class Record(CreatedUpdatedModel):
|
|||||||
return reverse('dns:record', args=[self.pk])
|
return reverse('dns:record', args=[self.pk])
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
record_type = record_type.upper()
|
||||||
if not self.address and not self.value:
|
if not self.address and not self.value:
|
||||||
raise ValidationError("DNS records must have either an IP address or a text value")
|
raise ValidationError("DNS records must have either an IP address or a text value")
|
||||||
|
|
||||||
@ -79,5 +104,17 @@ class Record(CreatedUpdatedModel):
|
|||||||
str(self.value) if self.value else '',
|
str(self.value) if self.value else '',
|
||||||
])
|
])
|
||||||
|
|
||||||
#def to_json(self):
|
def to_bind(self):
|
||||||
# return JSON
|
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),
|
||||||
|
' ',
|
||||||
|
' ; gen by netbox ( '+time.strftime('%A %B %d %Y %H:%M:%S',time.localtime())+' ) '
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ from django_tables2 import RequestConfig
|
|||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
@ -30,12 +31,24 @@ def zone(request, pk):
|
|||||||
zone = get_object_or_404(Zone.objects.all(), pk=pk)
|
zone = get_object_or_404(Zone.objects.all(), pk=pk)
|
||||||
records = Record.objects.filter(zone=zone)
|
records = Record.objects.filter(zone=zone)
|
||||||
record_count = len(records)
|
record_count = len(records)
|
||||||
|
bind_export = zone.to_bind(records)
|
||||||
|
|
||||||
return render(request, 'dns/zone.html', {
|
if request.GET.get('bind_export'):
|
||||||
'zone': zone,
|
response = HttpResponse(
|
||||||
'records': records,
|
bind_export,
|
||||||
'record_count': record_count,
|
content_type='text/plain'
|
||||||
})
|
)
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="netbox_{}.txt"'\
|
||||||
|
.format(zone.name)
|
||||||
|
return response
|
||||||
|
|
||||||
|
else:
|
||||||
|
return render(request, 'dns/zone.html', {
|
||||||
|
'zone': zone,
|
||||||
|
'records': records,
|
||||||
|
'record_count': record_count,
|
||||||
|
'bind_export': bind_export,
|
||||||
|
})
|
||||||
|
|
||||||
class ZoneEditView(PermissionRequiredMixin, ObjectEditView):
|
class ZoneEditView(PermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'dns.change_zone'
|
permission_required = 'dns.change_zone'
|
||||||
@ -94,9 +107,11 @@ class RecordListView(ObjectListView):
|
|||||||
def record(request, pk):
|
def record(request, pk):
|
||||||
|
|
||||||
record = get_object_or_404(Record.objects.all(), pk=pk)
|
record = get_object_or_404(Record.objects.all(), pk=pk)
|
||||||
|
bind_export = record.to_bind()
|
||||||
|
|
||||||
return render(request, 'dns/record.html', {
|
return render(request, 'dns/record.html', {
|
||||||
'record': record,
|
'record': record,
|
||||||
|
'bind_export': bind_export,
|
||||||
})
|
})
|
||||||
|
|
||||||
class RecordEditView(PermissionRequiredMixin, ObjectEditView):
|
class RecordEditView(PermissionRequiredMixin, ObjectEditView):
|
||||||
|
@ -102,4 +102,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong class="text-md-left">BIND Export</strong>
|
||||||
|
<a class="pull-right" id="bind_export_select" href="#">Select</a>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body">
|
||||||
|
<tr>
|
||||||
|
<td><pre id="bind_export" style="overflow: scroll;">{{ bind_export }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block javascript %}
|
||||||
|
<script>
|
||||||
|
$('#bind_export_select').click(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
if(document.selection) {
|
||||||
|
var range = document.body.createTextRange();
|
||||||
|
range.moveToElementText(document.getElementById('bind_export'));
|
||||||
|
range.select();
|
||||||
|
}
|
||||||
|
else if(window.getSelection) {
|
||||||
|
var range = document.createRange();
|
||||||
|
range.selectNode(document.getElementById('bind_export'));
|
||||||
|
window.getSelection().addRange(range);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form action="{% url 'dns:record_list' %}" method="get">
|
<form action="{% url 'dns:record_list' %}" method="get">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" name="name" class="form-control" placeholder="Name" {% if request.GET.q %}value="{{ request.GET.q }}" {% endif %}/>
|
<input type="text" name="name_or_value" class="form-control" placeholder="Name or value" {% if request.GET.q %}value="{{ request.GET.q }}" {% endif %}/>
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||||
|
@ -25,6 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
<a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}bind_export=yes" class="btn btn-success">
|
||||||
|
<span class="glyphicon glyphicon-export" aria-hidden="true"></span>
|
||||||
|
BIND Export
|
||||||
|
</a>
|
||||||
{% if perms.dns.change_zone %}
|
{% if perms.dns.change_zone %}
|
||||||
<a href="{% url 'dns:zone_edit' pk=zone.pk %}" class="btn btn-warning">
|
<a href="{% url 'dns:zone_edit' pk=zone.pk %}" class="btn btn-warning">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||||
@ -139,4 +143,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong class="text-md-left">BIND Export</strong>
|
||||||
|
<a class="pull-right" id="bind_export_select" href="#">Select</a>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body">
|
||||||
|
<tr>
|
||||||
|
<td><pre id="bind_export" style="overflow: scroll;">{{ bind_export }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block javascript %}
|
||||||
|
<script>
|
||||||
|
$('#bind_export_select').click(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
if(document.selection) {
|
||||||
|
var range = document.body.createTextRange();
|
||||||
|
range.moveToElementText(document.getElementById('bind_export'));
|
||||||
|
range.select();
|
||||||
|
}
|
||||||
|
else if(window.getSelection) {
|
||||||
|
var range = document.createRange();
|
||||||
|
range.selectNode(document.getElementById('bind_export'));
|
||||||
|
window.getSelection().addRange(range);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user