Refactors the views used to add and delete records.

Notable changes are:
* Refactored views, utilizing more of Djangos features, resulting in simpler
  code.
* URLs are now the same when initially filling out a form and when editing a
  submitted form which contained errors.
* Instead of a result page showing the response from the DNS server (which is
  now logged using Djangos "logging" functionality) the user is now redirected
  back to the record listing of the current zone when the action was successful
  or stays on the form page when an error occured. Proper success/error
  messages are shown in both cases by utilizing Djangos "messages"
  functionality.
This commit is contained in:
Daniel Roschka 2015-09-05 19:03:21 +02:00
parent 7b3a24364c
commit e13b42cd13
13 changed files with 257 additions and 274 deletions

View File

@ -17,7 +17,7 @@ class ZoneException(Exception):
class RecordException(Exception): class RecordException(Exception):
"""Thrown when there is an issue dealign with a Record. """Thrown when there is an issue dealing with a Record.
* Adding or deleting. * Adding or deleting.
""" """

View File

@ -17,7 +17,8 @@ class CustomUnicodeListField(forms.CharField):
try: try:
string_list = [str(cur_rr) for cur_rr in eval(value)] string_list = [str(cur_rr) for cur_rr in eval(value)]
except: except:
raise ValidationError("Error in converting Unicode list to list of Strings: %r" % value) raise ValidationError("Error in converting Unicode list to list "
"of strings: %r" % value)
return string_list return string_list
@ -37,7 +38,8 @@ class CustomStringPeriodSuffix(forms.CharField):
if new_string[-1] != ".": if new_string[-1] != ".":
new_string += "." new_string += "."
except: except:
raise ValidationError("Unable to stick a period on the end of your input: %r" % value) raise ValidationError("Unable to stick a period on the end of "
"your input: %r" % value)
return new_string return new_string
@ -50,13 +52,17 @@ class FormAddForwardRecord(forms.Form):
record_name = forms.RegexField(max_length=100, record_name = forms.RegexField(max_length=100,
regex="^[a-zA-Z0-9-_]+$", regex="^[a-zA-Z0-9-_]+$",
required=False) required=False)
record_type = forms.ChoiceField(choices=settings.RECORD_TYPE_CHOICES) record_type = forms.ChoiceField(choices=settings.RECORD_TYPE_CHOICES,
widget=forms.RadioSelect)
zone_name = forms.CharField(max_length=100) zone_name = forms.CharField(max_length=100)
record_data = forms.GenericIPAddressField() record_data = forms.GenericIPAddressField()
ttl = forms.ChoiceField(choices=settings.TTL_CHOICES) ttl = forms.ChoiceField(choices=settings.TTL_CHOICES,
widget=forms.RadioSelect)
create_reverse = forms.BooleanField(required=False) create_reverse = forms.BooleanField(required=False)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False) required=False,
widget=forms.RadioSelect,
empty_label=None)
class FormAddReverseRecord(forms.Form): class FormAddReverseRecord(forms.Form):
@ -66,12 +72,16 @@ class FormAddReverseRecord(forms.Form):
dns_server = forms.CharField(max_length=100) dns_server = forms.CharField(max_length=100)
record_name = forms.IntegerField(min_value=0, max_value=255) record_name = forms.IntegerField(min_value=0, max_value=255)
record_type = forms.RegexField(regex=r"^PTR$", record_type = forms.RegexField(regex=r"^PTR$",
error_messages={"invalid": "The only valid choice here is PTR."}) error_messages={
"invalid": "The only valid choice here "
"is PTR."})
zone_name = forms.CharField(max_length=100) zone_name = forms.CharField(max_length=100)
record_data = CustomStringPeriodSuffix(required=True) record_data = CustomStringPeriodSuffix(required=True)
ttl = forms.ChoiceField(choices=settings.TTL_CHOICES) ttl = forms.ChoiceField(choices=settings.TTL_CHOICES)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False) required=False,
widget=forms.RadioSelect,
empty_label=None)
create_reverse = forms.BooleanField(required=False) create_reverse = forms.BooleanField(required=False)
@ -83,9 +93,12 @@ class FormAddCnameRecord(forms.Form):
originating_record = forms.CharField(max_length=100) originating_record = forms.CharField(max_length=100)
cname = forms.RegexField(max_length=100, regex="^[a-zA-Z0-9-_]+$") cname = forms.RegexField(max_length=100, regex="^[a-zA-Z0-9-_]+$")
zone_name = forms.CharField(max_length=256) zone_name = forms.CharField(max_length=256)
ttl = forms.ChoiceField(choices=settings.TTL_CHOICES) ttl = forms.ChoiceField(choices=settings.TTL_CHOICES,
widget=forms.RadioSelect)
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False) required=False,
widget=forms.RadioSelect,
empty_label=None)
class FormDeleteRecord(forms.Form): class FormDeleteRecord(forms.Form):
@ -96,4 +109,6 @@ class FormDeleteRecord(forms.Form):
zone_name = forms.CharField(max_length=256) zone_name = forms.CharField(max_length=256)
rr_list = CustomUnicodeListField() rr_list = CustomUnicodeListField()
key_name = forms.ModelChoiceField(queryset=Key.objects.all(), key_name = forms.ModelChoiceField(queryset=Key.objects.all(),
required=False) required=False,
widget=forms.RadioSelect,
empty_label=None)

View File

@ -1,11 +1,13 @@
# Binder Helpers # Binder Helpers
# Standard Imports # Standard Imports
import logging
import re import re
import socket import socket
# 3rd Party # 3rd Party
import dns.query import dns.query
import dns.rcode
import dns.reversename import dns.reversename
import dns.tsig import dns.tsig
import dns.tsigkeyring import dns.tsigkeyring
@ -13,6 +15,7 @@ import dns.update
# App Imports # App Imports
from binder import models from binder import models
from binder.exceptions import KeyringException, RecordException
def add_record(dns_server, zone_name, record_name, record_type, record_data, def add_record(dns_server, zone_name, record_name, record_type, record_data,
@ -89,11 +92,13 @@ def delete_record(dns_server, rr_list, key_name):
"""Delete a list of DNS records passed as strings in rr_items.""" """Delete a list of DNS records passed as strings in rr_items."""
server = models.BindServer.objects.get(hostname=dns_server) server = models.BindServer.objects.get(hostname=dns_server)
logger = logging.getLogger('binder.helpers')
try: try:
transfer_key = models.Key.objects.get(name=key_name) transfer_key = models.Key.objects.get(name=key_name)
except models.Key.DoesNotExist: except models.Key.DoesNotExist as exc:
keyring = None logger.error(exc)
algorithm = None raise KeyringException("The specified TSIG key %s does not exist in "
"binders configuration." % key_name)
else: else:
keyring = transfer_key.create_keyring() keyring = transfer_key.create_keyring()
algorithm = transfer_key.algorithm algorithm = transfer_key.algorithm
@ -107,13 +112,19 @@ def delete_record(dns_server, rr_list, key_name):
keyring=keyring, keyring=keyring,
keyalgorithm=algorithm) keyalgorithm=algorithm)
dns_update.delete(record) dns_update.delete(record)
try:
output = send_dns_update(dns_update, output = send_dns_update(dns_update,
dns_server, dns_server,
server.dns_port, server.dns_port,
key_name) key_name)
except (KeyringException, RecordException) as exc:
delete_response.append({"description": "Delete Record: %s" % current_rr, delete_response.append({"description": exc,
"output": output}) "record": current_rr,
"success": False})
else:
delete_response.append({"description": output,
"record": current_rr,
"success": True})
return delete_response return delete_response
@ -123,11 +134,13 @@ def create_update(dns_server, zone_name, record_name, record_type, record_data,
"""Update/Create DNS record of name and type with passed data and ttl.""" """Update/Create DNS record of name and type with passed data and ttl."""
server = models.BindServer.objects.get(hostname=dns_server) server = models.BindServer.objects.get(hostname=dns_server)
logger = logging.getLogger('binder.helpers')
try: try:
transfer_key = models.Key.objects.get(name=key_name) transfer_key = models.Key.objects.get(name=key_name)
except models.Key.DoesNotExist: except models.Key.DoesNotExist as exc:
keyring = None logger.error(exc)
algorithm = None raise KeyringException("The specified TSIG key %s does not exist in "
"binders configuration." % key_name)
else: else:
keyring = transfer_key.create_keyring() keyring = transfer_key.create_keyring()
algorithm = transfer_key.algorithm algorithm = transfer_key.algorithm
@ -174,13 +187,21 @@ def send_dns_update(dns_message, dns_server, port, key_name):
Returns: Returns:
String output String output
""" """
logger = logging.getLogger('binder.helpers')
try: try:
output = dns.query.tcp(dns_message, dns_server, port=port) output = dns.query.tcp(dns_message, dns_server, port=port)
except dns.tsig.PeerBadKey: except dns.tsig.PeerBadKey as exc:
output = ("DNS server %s is not configured for TSIG key: %s." % logger.error(exc)
raise KeyringException("DNS server %s is not configured for TSIG key: %s." %
(dns_server, key_name)) (dns_server, key_name))
except dns.tsig.PeerBadSignature: except dns.tsig.PeerBadSignature as exc:
output = ("DNS server %s did like the TSIG signature we sent. Check " logger.error(exc)
"key %s for correctness." % (dns_server, key_name)) raise KeyringException("DNS server %s didn't like the TSIG signature "
"we sent. Check key %s for correctness." %
(dns_server, key_name))
logger.debug(output)
return_code = output.rcode()
if return_code != dns.rcode.NOERROR:
raise RecordException('Error when requesting DNS server %s: %s' %
(dns_server, dns.rcode.to_text(return_code)))
return output return output

View File

@ -1,5 +1,6 @@
# Django settings for binder project. # Django settings for binder project.
import os import os
from django.contrib.messages import constants as messages
SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) SITE_ROOT = os.path.dirname(os.path.realpath(__file__))
DEBUG = True DEBUG = True
@ -88,6 +89,38 @@ INSTALLED_APPS = (
TEST_RUNNER = 'django.test.runner.DiscoverRunner' TEST_RUNNER = 'django.test.runner.DiscoverRunner'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt' : "%d/%b/%Y %H:%M:%S"
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
'level': 'INFO'
},
'binder': {
'handlers': ['console'],
'level': 'DEBUG'
}
}
}
MESSAGE_TAGS = {
messages.ERROR: 'danger'
}
TTL_CHOICES = ((300, "5 minutes"), TTL_CHOICES = ((300, "5 minutes"),
(1800, "30 minutes"), (1800, "30 minutes"),
(3600, "1 hour"), (3600, "1 hour"),

View File

@ -10,7 +10,6 @@
<script src="{% static "sorttable.js" %}"></script> <script src="{% static "sorttable.js" %}"></script>
</head> </head>
{% endblock header %} {% endblock header %}
<body> <body>
<script src="{% static "jquery-2.1.3.min.js" %}"></script> <script src="{% static "jquery-2.1.3.min.js" %}"></script>
<script src="{% static "bootstrap/js/bootstrap.min.js" %}"></script> <script src="{% static "bootstrap/js/bootstrap.min.js" %}"></script>
@ -36,6 +35,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h2>{% block pageheader %}{% endblock pageheader %}</h2> <h2>{% block pageheader %}{% endblock pageheader %}</h2>
@ -44,23 +45,17 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% block errors %} {% if messages %}
{% if errors %} <ul id="messages" class="list-unstyled text-center" style="margin-bottom: 2em;">
<div class="alert alert-error"> {% for message in messages %}
Errors were encountered: <li role="alert" class="alert alert-{{ message.tags }} alert-dismissible"><button aria-label="Close" data-dismiss="alert" class="close" type="button"><span aria-hidden="true">×</span></button>{{ message.message }}</li>
<br /> {% endfor %}
{{ errors }} </ul>
</div>
{% endif %} {% endif %}
{% endblock errors %} {% block body %}{% endblock body %}
{% block body %}
{% endblock body %}
</div> </div>
</div> </div>
</div> </div>
{% block footer %}{% endblock footer %}
</body> </body>
{% block footer %}
{% endblock footer %}
</html> </html>

View File

@ -1,9 +1,9 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block pageheader %}Add CNAME record for {{ originating_record }}{% endblock pageheader %} {% block pageheader %}Add CNAME record for {{ form.originating_record.value }}.{{ form.zone_name.value }}{% endblock pageheader %}
{% block body %} {% block body %}
<form class="form-horizontal" action="{% url "add_cname_result" %}" method="post">{% csrf_token %} <form class="form-horizontal" action="{% url "add_cname" dns_server=dns_server zone_name=form.zone_name.value record_name=originating_record %}" method="post">{% csrf_token %}
<legend>Create CNAME record</legend> <legend>Create CNAME record</legend>
<div class="form-group{% if form.dns_server.errors %} has-error{% endif %}"> <div class="form-group{% if form.dns_server.errors %} has-error{% endif %}">
@ -24,7 +24,7 @@
<div class="form-group{% if form.originating_record.errors %} has-error{% endif %}"> <div class="form-group{% if form.originating_record.errors %} has-error{% endif %}">
<label for="originating_record" class="col-sm-3 control-label">Originating Record: </label> <label for="originating_record" class="col-sm-3 control-label">Originating Record: </label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<input type="text" id="originating_record" name="originating_record" class="form-control" value="{{originating_record}}" readonly="readonly"/> <input type="text" id="originating_record" name="originating_record" class="form-control" value="{{ form.originating_record.value }}" readonly="readonly"/>
</div> </div>
{% if form.originating_record.errors %} {% if form.originating_record.errors %}
<div class="col-sm-4 col-md-5"> <div class="col-sm-4 col-md-5">
@ -41,8 +41,8 @@
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<div class="input-group"> <div class="input-group">
<input id="cname" name="cname" type="text" class="form-control"{% if form.cname.value %} value="{{ form.cname.value }}"{% endif %}/> <input id="cname" name="cname" type="text" class="form-control"{% if form.cname.value %} value="{{ form.cname.value }}"{% endif %}/>
<span class="input-group-addon">.{{zone_name}}</span> <span class="input-group-addon">.{{ form.zone_name.value }}</span>
<input type="hidden" name="zone_name" value="{{zone_name}}"/> <input type="hidden" name="zone_name" value="{{ form.zone_name.value }}"/>
</div> </div>
</div> </div>
{% if form.cname.errors %} {% if form.cname.errors %}
@ -58,10 +58,8 @@
<label for="ttl" class="col-sm-3 control-label">TTL: </label> <label for="ttl" class="col-sm-3 control-label">TTL: </label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<select id="ttl" name="ttl" class="form-control"> <select id="ttl" name="ttl" class="form-control">
{% for ttl, description in ttl_choices %} {% for choice in form.ttl %}
<option value="{{ttl}}"> <option value="{{ choice.choice_value }}">{{ choice.choice_value }} ({{ choice.choice_label }})</option>
{{ttl}} ({{description}})
</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@ -79,8 +77,8 @@
<label for="key_name" class="col-sm-3 control-label">TSIG Key:</label> <label for="key_name" class="col-sm-3 control-label">TSIG Key:</label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<select id="key_name" name="key_name" class="form-control"> <select id="key_name" name="key_name" class="form-control">
{% for key in tsig_keys %} {% for key in form.key_name %}
<option value="{{key.id}}"{% if key == dns_server.default_transfer_key %} selected="selected"{% endif %}>{{key}}</option> <option value="{{ key.choice_value }}"{% if key.choice_value|add:0 == dns_server.default_transfer_key.id %} selected="selected"{% endif %}>{{ key.choice_label }}</option>
{% empty %} {% empty %}
<option selected="selected" value="" /> <option selected="selected" value="" />
{% endfor %} {% endfor %}
@ -100,7 +98,7 @@
<div class="col-sm-3"></div> <div class="col-sm-3"></div>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<button type="submit" class="btn btn-default">Save Changes</button> <button type="submit" class="btn btn-default">Save Changes</button>
<a href="{% url "zone_list" dns_server=dns_server zone_name=zone_name %}" class="btn btn-warning">Cancel</a> <a href="{% url "zone_list" dns_server=dns_server zone_name=form.zone_name.value %}" class="btn btn-warning">Cancel</a>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,9 +1,9 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block pageheader %}Add record in {{ zone_name }}{% endblock pageheader %} {% block pageheader %}Add record in {{ form.zone_name.value }}{% endblock pageheader %}
{% block body %} {% block body %}
<form class="form-horizontal" action="{% url "add_record_result" %}" method="POST">{% csrf_token %} <form class="form-horizontal" action="{% url "add_record" dns_server=dns_server zone_name=form.zone_name.value %}" method="POST">{% csrf_token %}
<legend>Create Record</legend> <legend>Create Record</legend>
<div class="form-group{% if form.dns_server.errors %} has-error{% endif %}"> <div class="form-group{% if form.dns_server.errors %} has-error{% endif %}">
@ -26,8 +26,8 @@
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<div class="input-group"> <div class="input-group">
<input id="record_name" name="record_name" type="text" class="form-control"{% if form.record_name.value %} value="{{ form.record_name.value }}"{% endif %} /> <input id="record_name" name="record_name" type="text" class="form-control"{% if form.record_name.value %} value="{{ form.record_name.value }}"{% endif %} />
<span class="input-group-addon">.{{zone_name}}</span> <span class="input-group-addon">.{{ form.zone_name.value }}</span>
<input type="hidden" name="zone_name" value="{{zone_name}}" /> <input type="hidden" name="zone_name" value="{{ form.zone_name.value }}" />
</div> </div>
</div> </div>
{% if form.record_name.errors %} {% if form.record_name.errors %}
@ -43,9 +43,9 @@
<label for="record_type" class="col-sm-3 control-label">Record Type:</label> <label for="record_type" class="col-sm-3 control-label">Record Type:</label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<select id="record_type" name="record_type" class="form-control"> <select id="record_type" name="record_type" class="form-control">
{% if "in-addr.arpa" not in zone_name and "ip6.arpa" not in zone_name %} {% if "in-addr.arpa" not in form.zone_name.value and "ip6.arpa" not in form.zone_name.value %}
{% for type, name in record_type_choices %} {% for choice in form.record_type %}
<option value="{{name}}">{{name}}</option> <option value="{{ choice.choice_value }}">{{ choice.choice_label }}</option>
{% endfor %} {% endfor %}
{% else %} {% else %}
<option value="PTR">PTR</option> <option value="PTR">PTR</option>
@ -80,10 +80,8 @@
<label for="ttl" class="col-sm-3 control-label">TTL: </label> <label for="ttl" class="col-sm-3 control-label">TTL: </label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<select id="ttl" name="ttl" class="form-control"> <select id="ttl" name="ttl" class="form-control">
{% for ttl, description in ttl_choices %} {% for choice in form.ttl %}
<option value="{{ttl}}"> <option value="{{ choice.choice_value }}">{{ choice.choice_value }} ({{ choice.choice_label }})</option>
{{ttl}} ({{description}})
</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@ -97,7 +95,7 @@
{% endif %} {% endif %}
</div> </div>
{% if "in-addr.arpa" not in zone_name and "ip.arpa" not in zone_name %} {% if "in-addr.arpa" not in form.zone_name.value and "ip.arpa" not in form.zone_name.value %}
<div class="form-group"> <div class="form-group">
<label for="create_reverse" class="col-sm-3 control-label checkbox">Create Reverse Record (PTR):</label> <label for="create_reverse" class="col-sm-3 control-label checkbox">Create Reverse Record (PTR):</label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
@ -110,8 +108,8 @@
<label for="key_name" class="col-sm-3 control-label">TSIG Key:</label> <label for="key_name" class="col-sm-3 control-label">TSIG Key:</label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<select id="key_name" name="key_name" class="form-control"> <select id="key_name" name="key_name" class="form-control">
{% for key in tsig_keys %} {% for key in form.key_name %}
<option value="{{key.id}}"{% if key == dns_server.default_transfer_key %} selected="selected"{% endif %}>{{key}}</option> <option value="{{ key.choice_value }}"{% if key.choice_value|add:0 == dns_server.default_transfer_key.id %} selected="selected"{% endif %}>{{ key.choice_label }}</option>
{% empty %} {% empty %}
<option selected="selected" value="" /> <option selected="selected" value="" />
{% endfor %} {% endfor %}
@ -131,7 +129,7 @@
<div class="col-sm-3"></div> <div class="col-sm-3"></div>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<button type="submit" class="btn btn-default">Save Changes</button> <button type="submit" class="btn btn-default">Save Changes</button>
<a href="{% url "zone_list" dns_server=dns_server zone_name=zone_name %}" class="btn btn-warning">Cancel</a> <a href="{% url "zone_list" dns_server=dns_server zone_name=form.zone_name.value %}" class="btn btn-warning">Cancel</a>
</div> </div>
</div> </div>
</form> </form>

View File

@ -3,7 +3,7 @@
{% block pageheader %}Delete record(s) in {{ zone_name }}{% endblock pageheader %} {% block pageheader %}Delete record(s) in {{ zone_name }}{% endblock pageheader %}
{% block body %} {% block body %}
<form class="form-horizontal" action="{% url "delete_record_result" %}" method="POST">{% csrf_token %} <form class="form-horizontal" action="{% url "delete_record" dns_server=dns_server zone_name=zone_name %}" method="POST">{% csrf_token %}
<legend>Delete Record</legend> <legend>Delete Record</legend>
<div class="row"> <div class="row">
@ -39,8 +39,8 @@
<label for="key_name" class="col-sm-3 control-label">TSIG Key:</label> <label for="key_name" class="col-sm-3 control-label">TSIG Key:</label>
<div class="col-sm-5 col-md-4"> <div class="col-sm-5 col-md-4">
<select id="key_name" name="key_name" class="form-control"> <select id="key_name" name="key_name" class="form-control">
{% for key in tsig_keys %} {% for key in form.key_name %}
<option value="{{key.id}}"{% if key == dns_server.default_transfer_key %} selected="selected"{% endif %}>{{key}}</option> <option value="{{ key.choice_value }}"{% if key.choice_value|add:0 == dns_server.default_transfer_key.id %} selected="selected"{% endif %}>{{ key.choice_label }}</option>
{% empty %} {% empty %}
<option selected="selected" value="" /> <option selected="selected" value="" />
{% endfor %} {% endfor %}

View File

@ -3,13 +3,9 @@
{% block pageheader %}Zone listing for {{ zone_name }} on {{ dns_server.hostname }}{% endblock pageheader %} {% block pageheader %}Zone listing for {{ zone_name }} on {{ dns_server.hostname }}{% endblock pageheader %}
{% block body %} {% block body %}
{% if not errors %} <p><a href="{% url "add_record" dns_server=dns_server zone_name=zone_name %}" class="btn btn-default">Add Record</a></p>
<a href="{% url "add_record" dns_server=dns_server zone_name=zone_name %}" class="btn btn-default">Add Record</a>
<form action="{% url "delete_record" %}" method="post">{% csrf_token %} <form action="{% url "delete_record" dns_server=dns_server zone_name=zone_name %}" method="post">{% csrf_token %}
<input type="hidden" name="dns_server" value="{{ dns_server.hostname }}">
<input type="hidden" name="zone_name" value="{{ zone_name }}">
<table class="sortable table table-condensed table-hover"> <table class="sortable table table-condensed table-hover">
<tr> <tr>
@ -48,7 +44,6 @@
{% endfor %} {% endfor %}
</table> </table>
<button class="btn btn-danger" type="submit">Delete Selected</button> <p><button class="btn btn-danger" type="submit">Delete Selected</button></p>
</form> </form>
{% endif %}
{% endblock body %} {% endblock body %}

View File

@ -1,18 +0,0 @@
{% extends "base.html" %}
{% block pageheader %}Record Result{% endblock pageheader %}
{% block body %}
<table class="table">
{% for current_response in response %}
<tr>
<th>Record</th>
<th>Output</th>
</tr>
<tr>
<td>{{ current_response.description }}</td>
<td><pre>{{ current_response.output }}</pre></td>
</tr>
{% endfor %}
</table>
{% endblock body %}

View File

@ -27,18 +27,6 @@ class GetTests(TestCase):
response = self.client.get(reverse("server_list")) response = self.client.get(reverse("server_list"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_GetResultRedirects(self):
"""GETing a /result/ URL should always redirect to /."""
response = self.client.get(reverse("add_record_result"), follow=True)
self.assertRedirects(response, reverse("index"))
self.assertEqual(response.status_code, 200)
response = self.client.get(reverse("delete_record_result"), follow=True)
self.assertRedirects(response, reverse("index"))
self.assertEqual(response.status_code, 200)
response = self.client.get(reverse("add_cname_result"), follow=True)
self.assertRedirects(response, reverse("index"))
self.assertEqual(response.status_code, 200)
def test_GetInvalidServer(self): def test_GetInvalidServer(self):
"""Get a zone list for a server not in the database.""" """Get a zone list for a server not in the database."""
server_name = "unconfigured.server.net" server_name = "unconfigured.server.net"
@ -64,20 +52,18 @@ class PostTests(TestCase):
def test_DeleteRecordInitial_Empty(self): def test_DeleteRecordInitial_Empty(self):
"""Ensure the initial deletion form works as expected with no RR list.""" """Ensure the initial deletion form works as expected with no RR list."""
dns_server = "testserver.test.net"
zone_name = "testzone1.test.net"
response = self.client.post(reverse("delete_record"), response = self.client.post(reverse("delete_record"),
{"dns_server": "testserver.test.net", {"dns_server": dns_server,
"zone_name": "testzone1.test.net", "zone_name": zone_name,
"rr_list": []}) "rr_list": []}, follow=True)
self.assertRedirects(response,
self.assertContains(response, reverse("zone_list",
'<input type="hidden" id="zone_name" name="zone_name" value="testzone1.test.net" />', kwargs={'dns_server': dns_server,
html=True) 'zone_name': zone_name}))
self.assertContains(response, self.assertEqual(response.status_code, 200)
'<input type="hidden" id="rr_list" name="rr_list" value="[]" />', self.assertContains(response, "Select at least one record for deletion.")
html=True)
self.assertContains(response,
'<input type="hidden" id="dns_server" name="dns_server" value="testserver.test.net" />',
html=True)
def test_DeleteRecordInitial(self): def test_DeleteRecordInitial(self):
@ -86,6 +72,8 @@ class PostTests(TestCase):
"zone_name": "testzone1.test.net", "zone_name": "testzone1.test.net",
"rr_list": ["testrecord1.testzone1.test.net", "rr_list": ["testrecord1.testzone1.test.net",
"testrecord2.testzone1.test.net"]}) "testrecord2.testzone1.test.net"]})
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.assertContains(response,
'<input type="hidden" id="zone_name" name="zone_name" value="testzone1.test.net" />', html=True) '<input type="hidden" id="zone_name" name="zone_name" value="testzone1.test.net" />', html=True)
self.assertContains(response, self.assertContains(response,

View File

@ -15,11 +15,6 @@ urlpatterns = patterns('',
url(r'^info/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/$', 'binder.views.view_zone_records', name="zone_list"), url(r'^info/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/$', 'binder.views.view_zone_records', name="zone_list"),
url(r'^add_record/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/$', 'binder.views.view_add_record', name="add_record"), url(r'^add_record/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/$', 'binder.views.view_add_record', name="add_record"),
url(r'^add_record/result/$', 'binder.views.view_add_record_result', name="add_record_result"),
url(r'^delete_record/$', 'binder.views.view_delete_record', name="delete_record"),
url(r'^delete_record/result/$', 'binder.views.view_delete_result', name="delete_record_result"),
url(r'^add_cname/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/(?P<record_name>.*?)/$', 'binder.views.view_add_cname_record', name="add_cname"), url(r'^add_cname/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/(?P<record_name>.*?)/$', 'binder.views.view_add_cname_record', name="add_cname"),
url(r'^add_cname_record/result/$', 'binder.views.view_add_cname_result', name="add_cname_result"), url(r'^delete_record/(?P<dns_server>[a-zA-Z0-9.-]+)/(?P<zone_name>[a-zA-Z0-9.-]+)/$', 'binder.views.view_delete_record', name="delete_record"),
) )

View File

@ -1,12 +1,12 @@
# Binder VIews # Binder VIews
# 3rd Party # 3rd Party
from django.conf import settings from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
# App Imports # App Imports
from binder import exceptions, forms, helpers, models from binder import forms, helpers, models
from binder.exceptions import KeyringException, RecordException, TransferException, ZoneException
def home_index(request): def home_index(request):
"""List the main index page for Binder.""" """List the main index page for Binder."""
@ -27,75 +27,49 @@ def view_server_list(request):
def view_server_zones(request, dns_server): def view_server_zones(request, dns_server):
"""Display the list of DNS zones a particular DNS host provides.""" """Display the list of DNS zones a particular DNS host provides."""
errors = ""
zone_array = {} zone_array = {}
this_server = get_object_or_404(models.BindServer, hostname=dns_server) this_server = get_object_or_404(models.BindServer, hostname=dns_server)
try: try:
zone_array = this_server.list_zones() zone_array = this_server.list_zones()
except exceptions.ZoneException, err: except ZoneException as exc:
errors = "Unable to list server zones. Error: %s" % err messages.error(request, "Unable to list server zones. Error: %s" % exc)
return render(request, "bcommon/list_server_zones.html", return render(request, "bcommon/list_server_zones.html",
{"errors": errors, {"dns_server": this_server,
"dns_server": this_server,
"zone_array": zone_array}) "zone_array": zone_array})
def view_zone_records(request, dns_server, zone_name): def view_zone_records(request, dns_server, zone_name):
"""Display the list of records for a particular zone.""" """Display the list of records for a particular zone."""
errors = ""
zone_array = {} zone_array = {}
this_server = get_object_or_404(models.BindServer, hostname=dns_server) this_server = get_object_or_404(models.BindServer, hostname=dns_server)
try: try:
zone_array = this_server.list_zone_records(zone_name) zone_array = this_server.list_zone_records(zone_name)
except exceptions.TransferException, err: except TransferException as exc:
return render(request, "bcommon/list_zone.html", return render(request, "bcommon/list_zone.html",
{"errors": err, {"zone_name": zone_name,
"zone_name": zone_name,
"dns_server": this_server}) "dns_server": this_server})
return render(request, "bcommon/list_zone.html", return render(request, "bcommon/list_zone.html",
{"zone_array": zone_array, {"zone_array": zone_array,
"dns_server": this_server, "dns_server": this_server,
"zone_name": zone_name, "zone_name": zone_name})
"errors": errors})
def view_add_record(request, dns_server, zone_name): def view_add_record(request, dns_server, zone_name):
"""View to provide form to add a DNS record.""" """View to allow to add A records."""
this_server = get_object_or_404(models.BindServer, hostname=dns_server) this_server = get_object_or_404(models.BindServer, hostname=dns_server)
return render(request, "bcommon/add_record_form.html", if request.method == 'POST':
{"dns_server": this_server,
"zone_name": zone_name,
"tsig_keys": models.Key.objects.all(),
"ttl_choices": settings.TTL_CHOICES,
"record_type_choices": settings.RECORD_TYPE_CHOICES})
def view_add_record_result(request):
"""Process the input given to add a DNS record."""
errors = ""
if request.method == "GET":
return redirect("/")
if "HTTP_REFERER" in request.META:
incoming_zone = request.META["HTTP_REFERER"].split("/")[-2]
if ("in-addr.arpa" in incoming_zone) or ("ip6.arpa" in incoming_zone):
form = forms.FormAddReverseRecord(request.POST)
else:
form = forms.FormAddForwardRecord(request.POST) form = forms.FormAddForwardRecord(request.POST)
else:
form = forms.FormAddForwardRecord(request.POST)
if form.is_valid(): if form.is_valid():
form_cleaned = form.cleaned_data form_cleaned = form.cleaned_data
try: try:
response = helpers.add_record(form_cleaned["dns_server"], helpers.add_record(form_cleaned["dns_server"],
str(form_cleaned["zone_name"]), str(form_cleaned["zone_name"]),
str(form_cleaned["record_name"]), str(form_cleaned["record_name"]),
str(form_cleaned["record_type"]), str(form_cleaned["record_type"]),
@ -103,108 +77,97 @@ def view_add_record_result(request):
form_cleaned["ttl"], form_cleaned["ttl"],
form_cleaned["key_name"], form_cleaned["key_name"],
form_cleaned["create_reverse"]) form_cleaned["create_reverse"])
except exceptions.RecordException, err: except (KeyringException, RecordException) as exc:
# TODO: Start using this exception. messages.error(request, "Adding %s.%s failed: %s" %
# What would cause this? (form_cleaned["record_name"], zone_name, exc))
errors = err else:
messages.success(request, "%s.%s was added successfully." %
return render(request, "bcommon/response_result.html", (form_cleaned["record_name"], zone_name))
{"errors": errors, return redirect('zone_list',
"response": response}) dns_server=dns_server,
zone_name=zone_name)
dns_server = models.BindServer.objects.get(hostname=request.POST["dns_server"]) else:
form = forms.FormAddForwardRecord(initial={'zone_name': zone_name})
return render(request, "bcommon/add_record_form.html", return render(request, "bcommon/add_record_form.html",
{"dns_server": dns_server, {"dns_server": this_server,
"zone_name": request.POST["zone_name"],
"tsig_keys": models.Key.objects.all(),
"ttl_choices": settings.TTL_CHOICES,
"record_type_choices": settings.RECORD_TYPE_CHOICES,
"form": form}) "form": form})
def view_add_cname_record(request, dns_server, zone_name, record_name): def view_add_cname_record(request, dns_server, zone_name, record_name):
"""Process given input to add a CNAME pointer.""" """View to allow to add CNAME records."""
this_server = get_object_or_404(models.BindServer, hostname=dns_server) this_server = get_object_or_404(models.BindServer, hostname=dns_server)
return render(request, "bcommon/add_cname_record_form.html", if request.method == 'POST':
{"dns_server": this_server,
"originating_record": "%s.%s" % (record_name, zone_name),
"zone_name": zone_name,
"ttl_choices": settings.TTL_CHOICES,
"tsig_keys": models.Key.objects.all()})
def view_add_cname_result(request):
"""Process input on the CNAME form and provide a response."""
if request.method == "GET":
return redirect("/")
errors = ""
add_cname_response = ""
form = forms.FormAddCnameRecord(request.POST) form = forms.FormAddCnameRecord(request.POST)
if form.is_valid(): if form.is_valid():
form_cleaned = form.cleaned_data form_cleaned = form.cleaned_data
try: try:
add_cname_response = helpers.add_cname_record( helpers.add_cname_record(form_cleaned["dns_server"],
form_cleaned["dns_server"], str(form_cleaned["zone_name"]),
form_cleaned["zone_name"], str(form_cleaned["cname"]),
form_cleaned["cname"], '%s.%s' % (str(form_cleaned["originating_record"]),
str(form_cleaned["originating_record"]), str(form_cleaned["zone_name"])),
form_cleaned["ttl"], form_cleaned["ttl"],
form_cleaned["key_name"]) form_cleaned["key_name"])
except exceptions.RecordException, err: except (KeyringException, RecordException) as exc:
errors = err messages.error(request, "Adding %s.%s failed: %s" %
(form_cleaned["record_name"], zone_name, exc))
return render(request, "bcommon/response_result.html", else:
{"response": add_cname_response, messages.success(request, "%s.%s was added successfully." %
"errors": errors}) (form_cleaned["record_name"], zone_name))
return redirect('zone_list',
dns_server = models.BindServer.objects.get(hostname=request.POST["dns_server"]) dns_server=dns_server,
zone_name=zone_name)
else:
form = forms.FormAddCnameRecord(initial={'originating_record': record_name,
'zone_name': zone_name})
return render(request, "bcommon/add_cname_record_form.html", return render(request, "bcommon/add_cname_record_form.html",
{"dns_server": dns_server, {"dns_server": this_server,
"zone_name": request.POST["zone_name"],
"record_name": request.POST["cname"],
"originating_record": request.POST["originating_record"],
"ttl_choices": settings.TTL_CHOICES,
"tsig_keys": models.Key.objects.all(),
"form": form}) "form": form})
def view_delete_record(request): def view_delete_record(request, dns_server, zone_name):
"""Provide the initial form for deleting records.""" """View to handle the deletion of records."""
if request.method == "GET": dns_server = models.BindServer.objects.get(hostname=dns_server)
return redirect("/")
dns_server = models.BindServer.objects.get(hostname=request.POST["dns_server"])
zone_name = request.POST["zone_name"]
rr_list = request.POST.getlist("rr_list") rr_list = request.POST.getlist("rr_list")
return render(request, "bcommon/delete_record_initial.html", if len(rr_list) == 0:
messages.error(request, "Select at least one record for deletion.")
return redirect('zone_list',
dns_server=dns_server,
zone_name=zone_name)
if request.method == 'POST':
form = forms.FormDeleteRecord(request.POST)
if form.is_valid():
form_cleaned = form.cleaned_data
rr_list = form_cleaned["rr_list"]
try:
response = helpers.delete_record(form_cleaned["dns_server"],
rr_list,
form_cleaned["key_name"])
except KeyringException as exc:
for record in rr_list:
messages.error(request, "Deleting %s.%s failed: %s" %
(record, zone_name, exc))
else:
for record in response:
if record['success'] == True:
messages.success(request, "%s.%s was removed successfully." %
(record['record'], zone_name))
else:
messages.error(request, "Deleting %s.%s failed: %s" %
(record['record'], zone_name, record['description']))
return redirect('zone_list',
dns_server=dns_server,
zone_name=zone_name)
else:
form = forms.FormDeleteRecord(initial={'zone_name': zone_name})
return render(request, "bcommon/delete_record.html",
{"dns_server": dns_server, {"dns_server": dns_server,
"zone_name": zone_name, "zone_name": zone_name,
"rr_list": rr_list, "rr_list": rr_list,
"tsig_keys": models.Key.objects.all()}) "form": form})
def view_delete_result(request):
"""View that deletes records and returns the response."""
if request.method == "GET":
return redirect("/")
form = forms.FormDeleteRecord(request.POST)
if form.is_valid():
clean_form = form.cleaned_data
else:
# TODO: What situations would cause this form
# not to validate?
print "in view_delete_result, form errors: %r" % form.errors
delete_result = helpers.delete_record(clean_form["dns_server"],
clean_form["rr_list"],
clean_form["key_name"])
return render(request, "bcommon/response_result.html",
{"response": delete_result})