diff --git a/templates/whoisdb/inetnum_detail.html b/templates/whoisdb/inetnum_detail.html
new file mode 100644
index 0000000..892589d
--- /dev/null
+++ b/templates/whoisdb/inetnum_detail.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+
+
+
Header
+
+ {{ inetnum }}
+ {{ inetnum.address }}/{{ inetnum.netmask }}
+
+
+
+
+{% endblock %}
+
diff --git a/templates/whoisdb/overview.html b/templates/whoisdb/overview.html
index 8893b4f..924c98b 100644
--- a/templates/whoisdb/overview.html
+++ b/templates/whoisdb/overview.html
@@ -48,7 +48,7 @@
{% include "whoisdb/handle_table_row.html" with obj=asnumber objType="AS Number" prefix="contact" %}
{% endfor %}
{% for netblock in netblocks %}
- {% include "whoisdb/handle_table_row.html" with obj=netblock objType="IP Netblock" prefix="contact" %}
+ {% include "whoisdb/handle_table_row.html" with obj=netblock objType="IP Netblock" prefix="inetnum" %}
{% endfor %}
diff --git a/whoisdb/forms.py b/whoisdb/forms.py
index 17d1a3a..f5467d9 100644
--- a/whoisdb/forms.py
+++ b/whoisdb/forms.py
@@ -6,6 +6,10 @@ from django import forms
from .models import Maintainer, Contact, InetNum
+import re
+import ipaddress
+
+
class WhoisObjectMixin(object):
def __init__(self, user, *args, **kwargs):
super(WhoisObjectMixin, self).__init__(*args, **kwargs)
@@ -24,9 +28,12 @@ class WhoisObjectMixin(object):
def clean(self):
cleaned_data = super(WhoisObjectMixin, self).clean()
- if cleaned_data.get("handle") == "AUTO":
+ if cleaned_data.get("handle") == "AUTO" and not self.errors:
cleaned_data['handle'] = self._meta.model.genGenericHandle(cleaned_data.get("name"))
+ return cleaned_data
+
+
class MntForm(forms.ModelForm):
class Meta:
model = Maintainer
@@ -38,11 +45,13 @@ class MntForm(forms.ModelForm):
if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.objects.filter(mnt_by=user.maintainer_set.all())
+
class MntInitialForm(MntForm):
class Meta:
model = Maintainer
fields = ['handle', 'description']
+
class ContactForm(WhoisObjectMixin, forms.ModelForm):
class Meta:
model = Contact
@@ -51,24 +60,76 @@ class ContactForm(WhoisObjectMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
+
class ContactInitialForm(ContactForm):
class Meta:
model = Contact
fields = ['handle', 'name']
+
class InetNumForm(WhoisObjectMixin, forms.ModelForm):
+ prefix = forms.CharField()
+ protectedFields = ['handle', 'protocol', 'parent_range', 'mnt_by', 'prefix']
+
class Meta:
model = InetNum
- fields = ['protocol', 'parent_range', 'address', 'description', 'mnt_lower']
+ fields = ['handle', 'protocol', 'parent_range', 'prefix', 'name', 'description', 'mnt_by', 'mnt_lower']
def __init__(self, lower=False, *args, **kwargs):
- super(MntForm, self).__init__(*args, **kwargs)
+ super(InetNumForm, self).__init__(*args, **kwargs)
+ print("args", args, kwargs)
self._editLower = lower
if 'admin_c' in self.fields:
- self.fields['admin_c'].queryset = Contact.objects.filter(mnt_by=self.user.maintainer_set.all())
+ self.fields['admin_c'].queryset = Contact.objects.filter(mnt_by__in=self.user.maintainer_set.all())
if self._editLower:
+ for key in self.protectedFields:
+ self.fields[key].disabled = True
+ self.fields[key].widget.attrs['readonly'] = False
+
+ def clean_prefix(self):
+ # make sure this is a subnet we're getting
+ print("HALLO")
+ 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")
+ try:
+ net = ipaddress.ip_network(net)
+ except ValueError as e:
+ raise forms.ValidationError(str(e))
+
+ return net
def clean(self):
# FIXME: Reset certain field sto instance:
- pass
+ cleaned_data = super(InetNumForm, self).clean()
+ if self._editLower:
+ # reset some fields, just in case
+ #for key in self.protectedFields:
+ # cleaned_data[key] = getattr(self.instance, key)
+ pass
+ else:
+ if all(x in cleaned_data for x in ('prefix', 'parent_range', 'protocol')):
+ prefix = cleaned_data['prefix']
+ parent = cleaned_data['parent_range']
+ parentNet = parent.getNetwork()
+
+ if cleaned_data['protocol'] != parent.protocol:
+ raise forms.ValidationError("Protocol type for prefix must be same as parent network")
+
+ # check if in parent block
+ if prefix.network_address not in parentNet or prefix.prefixlen < parentNet.prefixlen:
+ raise forms.ValidationError("Prefix must be inside parent network range")
+
+ # check if parent block has net that overlaps with us
+ for otherNet in parent.inetnum_set.all():
+ if self.instance and self.instance.pk == otherNet.pk:
+ continue
+
+ if otherNet.getNetwork().overlaps(prefix):
+ raise forms.ValidationError("The given prefix overlaps with network %s" % otherNet.handle)
+
+ self.instance.address = str(prefix.network_address)
+ self.instance.netmask = prefix.prefixlen
+
+ return cleaned_data
diff --git a/whoisdb/migrations/0008_auto_20170301_0003.py b/whoisdb/migrations/0008_auto_20170301_0003.py
new file mode 100644
index 0000000..7fb7ed2
--- /dev/null
+++ b/whoisdb/migrations/0008_auto_20170301_0003.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-03-01 00:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('whoisdb', '0007_asblock_mnt_lower'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='inetnum',
+ name='name',
+ field=models.CharField(default='', max_length=64),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='inetnum',
+ name='protocol',
+ field=models.CharField(choices=[('ipv4', 'IPv4'), ('ipv6', 'IPv6')], max_length=4),
+ ),
+ ]
diff --git a/whoisdb/models.py b/whoisdb/models.py
index 96dd462..874d52a 100644
--- a/whoisdb/models.py
+++ b/whoisdb/models.py
@@ -4,6 +4,8 @@ from django.urls import reverse
from dncore.models import User
from .validators import HandleValidator, HandleValidatorWithSuffix
+import ipaddress
+
class WhoisObject(models.Model):
class Meta:
@@ -79,9 +81,8 @@ class Maintainer(WhoisObject):
candidates = mntable.objects.filter(mnt_by=self).annotate(mntCount=models.Count('mnt_by')).filter(mntCount__lte=1)
for candidate in candidates:
reasons.append("Object %s would have no maintainers left." % candidate.handle)
-
- return reasons
+ return reasons
class MntdObject(WhoisObject):
@@ -112,9 +113,10 @@ class Contact(MntdObject):
candidates = contactable.objects.filter(admin_c=self).annotate(contactCount=models.Count('admin_c')).filter(contactCount__lte=1)
for candidate in candidates:
reasons.append("Object %s would have no contact left." % candidate.handle)
-
+
return reasons
+
class ASBlock(MntdObject):
handleSuffix = "ASB"
@@ -123,6 +125,7 @@ class ASBlock(MntdObject):
mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asblock_set', blank=True)
+
class ASNumber(MntdObject):
handleSuffix = "AS"
@@ -145,6 +148,20 @@ class InetNum(MntdObject):
address = models.GenericIPAddressField(db_index=True)
netmask = models.PositiveIntegerField()
parent_range = models.ForeignKey("InetNum", models.CASCADE, null=True, blank=True, default=None)
+ name = models.CharField(max_length=64)
description = models.CharField(max_length=64, blank=True)
mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_inetnum_set', blank=True)
+
+ def getNetwork(self):
+ return ipaddress.ip_network("%s/%s" % (self.address, self.netmask))
+
+ def get_absolute_url(self):
+ return reverse("whoisdb:inetnum-detail", kwargs={"handle": self.handle})
+
+ def getNoDeleteReasons(self):
+ reasons = []
+ if self.inetnum_set.all().count() > 0:
+ reasons.append("The following networks depend on this network: %s" % ", ".join(map(lambda _x: _x.handle, self.inetnum_set.all())))
+
+ return reasons
diff --git a/whoisdb/urls.py b/whoisdb/urls.py
index 177c43f..0273ca3 100644
--- a/whoisdb/urls.py
+++ b/whoisdb/urls.py
@@ -16,8 +16,7 @@ urlpatterns = [
url(r'^contact/delete/(?P[A-Z0-9-]+)/$', whoisdb_views.ContactDelete.as_view(), name='contact-delete'),
url(r'^inetnum/create/$', whoisdb_views.InetNumCreate.as_view(), name='inetnum-create'),
- url(r'^inetnum/show/(?P[A-Z0-9-]+)/$', whoisdb_views.ContactDetail.as_view(), name='inetnum-detail'),
- url(r'^inetnum/edit/(?P[A-Z0-9-]+)/$', whoisdb_views.ContactEdit.as_view(), name='inetnum-edit'),
- url(r'^inetnum/delete/(?P[A-Z0-9-]+)/$', whoisdb_views.ContactDelete.as_view(), name='inetnum-delete'),
+ url(r'^inetnum/show/(?P[A-Z0-9-]+)/$', whoisdb_views.InetNumDetail.as_view(), name='inetnum-detail'),
+ url(r'^inetnum/edit/(?P[A-Z0-9-]+)/$', whoisdb_views.InetNumEdit.as_view(), name='inetnum-edit'),
+ url(r'^inetnum/delete/(?P[A-Z0-9-]+)/$', whoisdb_views.InetNumDelete.as_view(), name='inetnum-delete'),
]
-
diff --git a/whoisdb/views.py b/whoisdb/views.py
index b0f2d98..bf6f5cb 100644
--- a/whoisdb/views.py
+++ b/whoisdb/views.py
@@ -10,13 +10,14 @@ from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber
from .forms import MntForm, MntInitialForm, ContactForm, ContactInitialForm, InetNumForm
from .generic import DeleteCheckView
+
@login_required
def dbDashboard(request):
mnts = request.user.maintainer_set.all()
contacts = Contact.objects.filter(mnt_by__in=mnts)
- netblocks = InetNum.objects.filter(Q(mnt_by__in=mnts)|Q(mnt_lower__in=mnts))
- asblocks = ASBlock.objects.filter(Q(mnt_by__in=mnts)|Q(mnt_lower__in=mnts))
- asnumbers = ASNumber.objects.filter(Q(mnt_by__in=mnts)|Q(mnt_lower__in=mnts))
+ netblocks = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
+ asblocks = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
+ asnumbers = ASNumber.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
mntForm = contactForm = None
if mnts.count() == 0:
@@ -49,6 +50,7 @@ def dbDashboard(request):
return render(request, "whoisdb/overview.html", {"mnts": mnts, "contacts": contacts, "mntForm": mntForm, "contactForm": contactForm, "netblocks": netblocks, "asblocks": asblocks, "asnumbers": asnumbers})
+
class MaintainerCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html"
form_class = MntForm
@@ -71,6 +73,7 @@ class MaintainerCreate(LoginRequiredMixin, CreateView):
return super(MaintainerCreate, self).form_valid(form)
+
class MaintainerEdit(LoginRequiredMixin, UpdateView):
template_name = "whoisdb/maintainer_edit.html"
model = Maintainer
@@ -86,6 +89,7 @@ class MaintainerEdit(LoginRequiredMixin, UpdateView):
def get_queryset(self):
return self.model.objects.filter(auth=self.request.user)
+
class MaintainerDelete(LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html"
model = Maintainer
@@ -95,7 +99,7 @@ class MaintainerDelete(LoginRequiredMixin, DeleteCheckView):
def get_queryset(self):
return self.model.objects.filter(auth=self.request.user)
-
+
class MaintainerDetail(LoginRequiredMixin, DetailView):
model = Maintainer
@@ -103,12 +107,14 @@ class MaintainerDetail(LoginRequiredMixin, DetailView):
slug_url_kwarg = "handle"
context_object_name = "mnt"
+
class ContactDetail(DetailView):
model = Contact
slug_field = "handle"
slug_url_kwarg = "handle"
context_object_name = "contact"
+
class ContactEdit(LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html"
model = Contact
@@ -125,6 +131,7 @@ class ContactEdit(LoginRequiredMixin, UpdateView):
# FIXME: we need all maintainers to be available. autofill own maintainers
return self.model.objects.filter(mnt_by__in=self.request.user.maintainer_set.all())
+
class ContactCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html"
form_class = ContactForm
@@ -138,6 +145,7 @@ class ContactCreate(LoginRequiredMixin, CreateView):
}
return kwargs
+
class ContactDelete(LoginRequiredMixin, DeleteCheckView):
template_name = "whoisdb/obj_delete.html"
model = Contact
@@ -148,6 +156,7 @@ class ContactDelete(LoginRequiredMixin, DeleteCheckView):
def get_queryset(self):
return self.model.objects.filter(mnt_by__in=self.request.user.maintainer_set.all())
+
# InetNum
class InetNumCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html"
@@ -158,7 +167,51 @@ class InetNumCreate(LoginRequiredMixin, CreateView):
kwargs["user"] = self.request.user
kwargs["initial"] = {
"handle": "AUTO",
- "type": Contact.TYPE_PERSON
}
+
return kwargs
+
+class InetNumDetail(DetailView):
+ model = InetNum
+ slug_field = "handle"
+ slug_url_kwarg = "handle"
+ context_object_name = "inetnum"
+
+
+class InetNumEdit(LoginRequiredMixin, UpdateView):
+ template_name = "whoisdb/obj_edit.html"
+ model = InetNum
+ form_class = InetNumForm
+ slug_field = "handle"
+ slug_url_kwarg = "handle"
+
+ def get_form_kwargs(self, *args, **kwargs):
+ kwargs = super(InetNumEdit, self).get_form_kwargs(*args, **kwargs)
+ kwargs["user"] = self.request.user
+
+ mnts = self.request.user.maintainer_set.all()
+ if not any(mnt in self.object.mnt_by.all() for mnt in mnts):
+ # we are in mnt_lower
+ kwargs["lower"] = True
+
+ kwargs["initial"] = {'prefix': str(self.object.getNetwork())}
+
+ return kwargs
+
+ def get_queryset(self):
+ # FIXME: we need all maintainers to be available. autofill own maintainers
+ mnts = self.request.user.maintainer_set.all()
+ return self.model.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
+
+
+class InetNumDelete(LoginRequiredMixin, DeleteCheckView):
+ template_name = "whoisdb/obj_delete.html"
+ model = InetNum
+ slug_field = "handle"
+ slug_url_kwarg = "handle"
+ success_url = reverse_lazy("whoisdb:dashboard")
+
+ def get_queryset(self):
+ mnts = self.request.user.maintainer_set.all()
+ return self.model.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts))