diff --git a/dncore/forms.py b/dncore/forms.py
new file mode 100644
index 0000000..8624c32
--- /dev/null
+++ b/dncore/forms.py
@@ -0,0 +1,8 @@
+from django.contrib.auth.forms import UserCreationForm
+from .models import User
+
+
+class CustomUserCreationForm(UserCreationForm):
+ class Meta:
+ model = User
+ fields = ("username",)
diff --git a/dncore/urls.py b/dncore/urls.py
index e4dbd30..d5c296b 100644
--- a/dncore/urls.py
+++ b/dncore/urls.py
@@ -7,7 +7,7 @@ urlpatterns = [
url(r'^$', dncore_views.dashboard, name='dashboard'),
url(r'^login/$', auth_views.login, name='login'),
- url(r'^login/$', auth_views.login, name='register'),
+ url(r'^register/$', dncore_views.RegisterUser.as_view(), name='register'),
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
url(r'^profile/$', dncore_views.profile, name='profile'),
]
diff --git a/dncore/views.py b/dncore/views.py
index 3d11c7d..9b00b2f 100644
--- a/dncore/views.py
+++ b/dncore/views.py
@@ -1,11 +1,17 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.db.models import Q
+from django.views.generic import CreateView
+from django.urls import reverse_lazy
+from django.contrib import messages
+
from whoisdb.models import ASNumber, InetNum
from domains.models import Domain
from rrequests.models import Request
+from .forms import CustomUserCreationForm
+
@login_required
def profile(request):
@@ -25,3 +31,15 @@ def dashboard(request):
def index(request):
return render(request, "index.html", {})
+
+
+class RegisterUser(CreateView):
+ template_name = "dncore/registration.html"
+ form_class = CustomUserCreationForm
+ success_url = reverse_lazy("user:login")
+
+ def form_valid(self, form):
+ ret = super(RegisterUser, self).form_valid(form)
+ messages.success(self.request, "You successfully registered as user %s and can now log in!" % form.instance.username)
+
+ return ret
diff --git a/domains/migrations/0002_auto_20170321_1854.py b/domains/migrations/0002_auto_20170321_1854.py
new file mode 100644
index 0000000..00875ba
--- /dev/null
+++ b/domains/migrations/0002_auto_20170321_1854.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-03-21 18:54
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('domains', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='reversezone',
+ name='created',
+ field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='reversezone',
+ name='last_modified',
+ field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+ preserve_default=False,
+ ),
+ ]
diff --git a/domains/models.py b/domains/models.py
index c39ad84..428beb3 100644
--- a/domains/models.py
+++ b/domains/models.py
@@ -1,7 +1,7 @@
from django.db import models
from django.urls import reverse
-from whoisdb.models import MntdObject, Contact, InetNum
+from whoisdb.models import MntdObject, WhoisObject, Contact, InetNum
import ipaddress
@@ -21,6 +21,9 @@ class Nameserver(MntdObject):
admin_c = models.ManyToManyField(Contact)
+ def getPK(self):
+ return self.name
+
def get_absolute_url(self):
return reverse("domains:nameserver-show", args=(self.name,))
@@ -36,6 +39,9 @@ class Domain(MntdObject):
nameservers = models.ManyToManyField(Nameserver, blank=True)
admin_c = models.ManyToManyField(Contact)
+ def getPK(self):
+ return self.name
+
def get_absolute_url(self):
return reverse("domains:domain-show", args=(self.name,))
@@ -52,13 +58,18 @@ class Domain(MntdObject):
return reasons
-class ReverseZone(models.Model):
+class ReverseZone(WhoisObject):
+ handle = None
+
parentNet = models.ForeignKey(InetNum)
address = models.GenericIPAddressField(db_index=True)
netmask = models.PositiveIntegerField()
nameservers = models.ManyToManyField(Nameserver)
+ def getPK(self):
+ return self.pk
+
def prefix(self):
""" Helper function, mainly used in templates """
return "%s/%s" % (self.address, self.netmask)
diff --git a/domains/views.py b/domains/views.py
index 848d99f..a337f56 100644
--- a/domains/views.py
+++ b/domains/views.py
@@ -18,7 +18,7 @@ def overview(request):
# get all domains and nameservers
domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
nameservers = Nameserver.objects.filter(mnt_by__in=mnts).distinct()
- reversezones = ReverseZone.objects.filter(parentNet__mnt_by__in=mnts).distinct()
+ reversezones = ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers, 'reversezones': reversezones})
@@ -134,7 +134,7 @@ class ReverseZoneEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
kwargs = super(ReverseZoneEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user
- if not "initial" in kwargs:
+ if "initial" not in kwargs:
kwargs["initial"] = {}
kwargs["initial"]["prefix"] = self.object.prefix()
diff --git a/templates/dncore/registration.html b/templates/dncore/registration.html
new file mode 100644
index 0000000..557a2e0
--- /dev/null
+++ b/templates/dncore/registration.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+
+{% load crispy_forms_tags %}
+
+{% block content %}
+
+
+
+
Registration
+
+
+ Note: Please choose a sane username.
+
+
+
+
+
+
+{% endblock %}
+
diff --git a/templates/domains/handle_show.html b/templates/domains/handle_show.html
index b6881b3..1d909ba 100644
--- a/templates/domains/handle_show.html
+++ b/templates/domains/handle_show.html
@@ -18,9 +18,9 @@
{% if object|userCanEdit:user %}
Actions |
- {% with "domains:"|add:object.getClassName|lower|add:"-edit" as editView %}
- {% with "domains:"|add:object.getClassName|lower|add:"-delete" as deleteView %}
- Edit object, Delete object |
+ {% with request.resolver_match.namespaces.0|add:":"|add:object.getClassName|lower|add:"-edit" as editView %}
+ {% with request.resolver_match.namespaces.0|add:":"|add:object.getClassName|lower|add:"-delete" as deleteView %}
+ Edit object, Delete object |
{% endwith %}
{% endwith %}
diff --git a/whoisdb/forms.py b/whoisdb/forms.py
index 4a0cf8d..66e2451 100644
--- a/whoisdb/forms.py
+++ b/whoisdb/forms.py
@@ -2,8 +2,8 @@ from django import forms
from django.db.models import Case, When, IntegerField
from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber
+from .validators import HandleValidatorWithSuffix, IP46CIDRValidator
-import re
import ipaddress
@@ -15,31 +15,36 @@ class WhoisObjectFormMixin(object):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self._create = False
- self.fields['handle'].widget.attrs['readonly'] = True
+ #self.fields['handle'].disabled = True
else:
self._create = True
+ self.fields['handle'].help_text = "Handle for this object in uppercase with a suffix of -%s" % instance.handleSuffix
+
# only show users contacts and already present contacts
mnts = self._user.maintainer_set.all()
if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.getMntQueryset(mnts, self.instance, "admin_c")
def clean_handle(self):
- if not self._create:
- return self.instance.handle
- else:
- return self.cleaned_data['handle']
+ HandleValidatorWithSuffix(self.instance.handleSuffix)(self.cleaned_data['handle'])
+ return self.cleaned_data['handle']
def clean(self):
cleaned_data = super(WhoisObjectFormMixin, self).clean()
if cleaned_data.get("handle") == "AUTO" and not self.errors:
- cleaned_data['handle'] = self._meta.model.genGenericHandle(cleaned_data.get("name"))
+ name = cleaned_data.get("name")
+ if name is None:
+ name = self._user.username
+
+ cleaned_data['handle'] = self._meta.model.genGenericHandle(name)
return cleaned_data
class MntFormMixin(object):
protectedFields = []
+
def __init__(self, lower=False, *args, **kwargs):
super(MntFormMixin, self).__init__(*args, **kwargs)
@@ -60,6 +65,7 @@ class MntFormMixin(object):
if "mnt_lower" in self.fields:
self.fields["mnt_lower"].queryset = mntQs
+
class MntForm(WhoisObjectFormMixin, forms.ModelForm):
class Meta:
model = Maintainer
@@ -80,7 +86,8 @@ class ContactForm(WhoisObjectFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
- self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
+ if "mnt_by" in self.fields:
+ self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
class ContactInitialForm(ContactForm):
@@ -91,7 +98,7 @@ class ContactInitialForm(ContactForm):
class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
prefix = forms.CharField()
- protectedFields = ['handle', 'protocol', 'parent_range', 'mnt_by', 'prefix']
+ protectedFields = ['protocol', 'parent_range', 'mnt_by', 'prefix']
class Meta:
model = InetNum
@@ -114,9 +121,9 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
def clean_prefix(self):
# make sure this is a subnet we're getting
- net = self.cleaned_data['prefix']
- if not re.match(r"[0-9:.]+/[0-9]+", net):
- raise forms.ValidationError("Address needs to be a subnet in the format of ip/cidr")
+ net = self.cleaned_data['prefix'].lower()
+ IP46CIDRValidator(net)
+
try:
net = ipaddress.ip_network(net)
except ValueError as e:
@@ -126,9 +133,9 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
def clean_parent_range(self):
parent_range = self.cleaned_data.get('parent_range', None)
-
+
# allow parent range to be unset for already present objects
- if not parent_range and (self._create or not self._create and self.instance.parent_range):
+ if not parent_range and (self._create or not self._create and self.instance.parent_range):
raise forms.ValidationError("Parent range must be set")
if not self._create and parent_range:
@@ -171,8 +178,9 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
return cleaned_data
+
class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
- protectedFields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'mnt_by']
+ protectedFields = ['parent_block', 'asBegin', 'asEnd', 'mnt_by']
# FIXME: Filter blocks
class Meta:
@@ -190,6 +198,23 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
mnts = self._user.maintainer_set.all()
self.fields['parent_block'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "parent_block")
+ def clean_parent_block(self):
+ parent_block = self.cleaned_data.get('parent_block', None)
+
+ # allow parent range to be unset for already present objects
+ if not parent_block and (self._create or not self._create and self.instance.parent_block):
+ raise forms.ValidationError("Parent block must be set")
+
+ if not self._create and parent_block:
+ # make sure we don't have circular dependencies
+ obj = parent_block
+ while obj.parent_block:
+ if obj.pk == self.instance.pk:
+ raise forms.ValidationError("No circular dependencies allowed")
+ obj = obj.parent_block
+
+ return parent_block
+
def clean(self):
cleaned_data = super(ASBlockForm, self).clean()
@@ -198,7 +223,7 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
asEnd = cleaned_data['asEnd']
parent = cleaned_data['parent_block']
# check if somebody is already using this block
-
+
# check if in range
if asBegin > asEnd:
raise forms.ValidationError("AS beginning must be smaller or equal to AS end")
@@ -207,7 +232,6 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
if parent.asnumber_set.count() > 0:
raise forms.ValidationError("The parent AS block is already references by following AS number objects: %s" % (", ".join(map(lambda _x: _x.handle, parent.asnumber_set.all())),))
-
# check if same range
if not (asBegin >= parent.asBegin and asEnd <= parent.asEnd):
raise forms.ValidationError("AS beginning and end must be inside the range of the parent AS block")
@@ -221,14 +245,14 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
continue
if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \
- asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
- raise forms.ValidationError("Block overlaps with block %s" % block.handle)
-
+ asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
+ raise forms.ValidationError("Block overlaps with block %s" % block.handle)
return cleaned_data
+
class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
- protectedFields = ['handle', 'asblock', 'number', 'mnt_by']
+ protectedFields = ['asblock', 'number', 'mnt_by']
class Meta:
model = ASNumber
@@ -267,4 +291,3 @@ class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
# has already other asblock?
if block.asblock_set.count() > 0:
raise forms.ValidationError("The given AS block is already references by following sub AS blocks: %s" % (", ".join(map(lambda _x: _x.handle, block.asblock_set.all())),))
-
diff --git a/whoisdb/generic.py b/whoisdb/generic.py
index 9cdabeb..b2dce2e 100644
--- a/whoisdb/generic.py
+++ b/whoisdb/generic.py
@@ -3,9 +3,10 @@ from django.http import HttpResponseRedirect
from django.contrib import messages
from django.db.models import Q
+
class DeleteCheckView(DeleteView):
""" Check if object actually can be deleted. Provide reasons to template
- if not.
+ if not.
"""
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -17,7 +18,7 @@ class DeleteCheckView(DeleteView):
return self.get(request, *args, **kwargs)
else:
self.object.delete()
- messages.info(request, "Object %s has been deleted" % self.object.handle)
+ messages.info(request, "Object %s has been deleted" % str(self.object))
return HttpResponseRedirect(success_url)
def get_context_data(self, **kwargs):
diff --git a/whoisdb/helpers.py b/whoisdb/helpers.py
index de25cb6..ca256c4 100644
--- a/whoisdb/helpers.py
+++ b/whoisdb/helpers.py
@@ -1,6 +1,7 @@
-import whoisdb.models
+import whoisdb.models
import domains.models
+
def _addFields(fields, obj, fieldNames):
for fieldName in fieldNames:
fields.append((fieldName.capitalize().replace("_", " "), getattr(obj, fieldName)))
@@ -9,7 +10,7 @@ def _addFields(fields, obj, fieldNames):
def getWhoisObjectFields(obj, owner):
fields = []
- if hasattr(obj, "handle"):
+ if getattr(obj, "handle", None):
_addFields(fields, obj, ["handle"])
c = type(obj)
@@ -34,12 +35,13 @@ def getWhoisObjectFields(obj, owner):
elif c == domains.models.Nameserver:
_addFields(fields, obj, ["name", "glueIPv4", "glueIPv6", "mnt_by", "admin_c"])
elif c == domains.models.ReverseZone:
- _addFields(fields, obj, ["name"])
+ #_addFields(fields, obj, ["name"])
fields.append(("Address CIDR", obj.prefix()))
_addFields(fields, obj, ["parentNet", "nameservers"])
return fields
+
def guessWhoisObject(self, handle):
# is it a normal handle?
pass
diff --git a/whoisdb/models.py b/whoisdb/models.py
index eb9cac4..d9e70d1 100644
--- a/whoisdb/models.py
+++ b/whoisdb/models.py
@@ -17,6 +17,20 @@ class WhoisObject(models.Model):
created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now_add=True)
+ #def __init__(self, *args, **kwargs):
+ # super(WhoisObject, self).__init__(*args, **kwargs)
+
+ # if getattr(self, "handle"):
+ # field = self._meta.get_field("handle")
+ # if HandleValidatorWithSuffix not in map(type, field.validators):
+ # print(self.handle, "NOOOOT")
+ # field.validators.append(HandleValidatorWithSuffix(self.handleSuffix))
+ # else:
+ # print(self.handle, list(map(type, field.validators)))
+
+ def getPK(self):
+ return self.handle
+
def __str__(self):
return self.handle
@@ -120,13 +134,14 @@ class MntdObject(WhoisObject):
qs = clazz.objects.filter(mntQ)
if attr and instance and instance.pk:
- if type(getattr(instance, attr)) == models.ManyToManyField:
+ if type(instance._meta.get_field(attr)) == models.ManyToManyField:
qs |= getattr(instance, attr).all()
- else:
- qs |= clazz.objects.filter(pk=instance.pk)
+ elif getattr(instance, attr) is not None:
+ qs |= clazz.objects.filter(pk=getattr(instance, attr).pk)
return qs.distinct()
+
class Contact(MntdObject):
handleSuffix = "DN"
TYPE_PERSON = 'PERSON'
@@ -205,6 +220,7 @@ class ASNumber(MntdObject):
def getResource(self):
return str(self.number)
+
class InetNum(MntdObject):
handleSuffix = "NET"
diff --git a/whoisdb/validators.py b/whoisdb/validators.py
index 02fc9d4..bf62130 100644
--- a/whoisdb/validators.py
+++ b/whoisdb/validators.py
@@ -10,7 +10,7 @@ import ipaddress
@deconstructible
class HandleValidator(validators.RegexValidator):
- regex = r'^(?:[A-Z]+[0-9]+(-[A-Z]+)|AUTO)'
+ regex = r'^(?:[A-Z]+[0-9]*(-[A-Z]+)|AUTO)'
message = _(
'Enter a valid handle (all uppercase)'
)
@@ -22,15 +22,16 @@ class HandleValidatorWithSuffix(validators.RegexValidator):
flags = re.ASCII if six.PY3 else 0
def __init__(self, suffix):
- self.regex = r'^(?:[A-Z]+[0-9]+-%s|AUTO)' % re.escape(suffix)
+ self.regex = r'^(?:[A-Z]+[0-9]*-%s|AUTO)' % re.escape(suffix)
self.message = _(
- 'Enter a valid handle with suffix %s (all uppercase)' % suffix
+ 'Enter a valid handle with suffix %s (all uppercase), e.g. FOO3-%s' % (suffix, suffix)
)
super(HandleValidatorWithSuffix, self).__init__()
+
def IP46CIDRValidator(value):
- if not re.match(r"[0-9:.]+/[0-9]+", value):
+ if not re.match(r"[0-9a-fA-F:.]+/[0-9]+", value):
raise ValidationError("Address needs to be a subnet in the format of ip/cidr")
try:
diff --git a/whoisdb/views.py b/whoisdb/views.py
index e2450b5..764d6e8 100644
--- a/whoisdb/views.py
+++ b/whoisdb/views.py
@@ -10,6 +10,7 @@ from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber
from .forms import MntForm, MntInitialForm, ContactForm, ContactInitialForm, InetNumForm, ASBlockForm, ASNumberForm
from .generic import DeleteCheckView, MntGenericMixin
+
@login_required
def createObjectOverview(request):
mnts = request.user.maintainer_set.all()
@@ -18,6 +19,7 @@ def createObjectOverview(request):
return render(request, "whoisdb/create_overview.html", {"netblocks": netblocks, "asblocks": asblocks})
+
@login_required
def dbDashboard(request):
mnts = request.user.maintainer_set.all()
@@ -31,7 +33,7 @@ def dbDashboard(request):
mntForm = contactForm = None
if request.method == "POST":
mntForm = MntInitialForm(user=request.user, data=request.POST, prefix="mnt")
- contactForm = ContactInitialForm(user=request.user, person=True, data=request.POST, prefix="contact")
+ contactForm = ContactInitialForm(user=request.user, data=request.POST, prefix="contact")
if mntForm.is_valid() and contactForm.is_valid():
mnt = mntForm.save(commit=False)
mnt.handleAuto(request.user.username)
@@ -51,7 +53,7 @@ def dbDashboard(request):
return HttpResponseRedirect(reverse("whoisdb:dashboard"))
else:
mntForm = MntInitialForm(user=request.user, prefix="mnt", initial={'handle': 'AUTO', 'description': 'Primary maintainer of %s' % request.user.username})
- contactForm = ContactInitialForm(user=request.user, person=True, initial={'handle': 'AUTO', 'name': request.user.username.capitalize()}, prefix='contact')
+ contactForm = ContactInitialForm(user=request.user, initial={'handle': 'AUTO', 'name': request.user.username.capitalize()}, prefix='contact')
return render(request, "whoisdb/overview.html", {"mnts": mnts, "contacts": contacts, "mntForm": mntForm, "contactForm": contactForm, "netblocks": netblocks, "asblocks": asblocks, "asnumbers": asnumbers})
@@ -239,6 +241,7 @@ class ASBlockDetail(DetailView):
slug_url_kwarg = "handle"
context_object_name = "asblock"
+
class ASBlockEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html"
model = ASBlock
@@ -261,6 +264,7 @@ class ASBlockDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard")
+
# asnumber
class ASNumberCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html"
@@ -303,4 +307,3 @@ class ASNumberDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
slug_field = "handle"
slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard")
-