Noot! Domains working

This commit is contained in:
Sebastian Lohff 2017-03-15 02:22:08 +01:00
parent 69abba4d36
commit aa6313b464
11 changed files with 264 additions and 94 deletions

View File

@ -32,12 +32,12 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'crispy_forms', 'crispy_forms',
'dncore', 'dncore',
'whoisdb', 'whoisdb',
@ -46,31 +46,31 @@ INSTALLED_APPS = [
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
ROOT_URLCONF = 'dnmgmt.urls' ROOT_URLCONF = 'dnmgmt.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "templates/")], 'DIRS': [os.path.join(BASE_DIR, "templates/")],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'dnmgmt.wsgi.application' WSGI_APPLICATION = 'dnmgmt.wsgi.application'
@ -80,10 +80,10 @@ WSGI_APPLICATION = 'dnmgmt.wsgi.application'
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
} }
} }
@ -91,18 +91,18 @@ DATABASES = {
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
}, },
] ]
@ -126,14 +126,13 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "cstatic") STATIC_ROOT = os.path.join(BASE_DIR, "cstatic")
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
] ]
CRISPY_TEMPLATE_PACK = 'bootstrap3' CRISPY_TEMPLATE_PACK = 'bootstrap3'
MESSAGE_TAGS = { MESSAGE_TAGS = {
messages.ERROR: 'danger', messages.ERROR: 'danger',
} }
AUTH_USER_MODEL = 'dncore.User' AUTH_USER_MODEL = 'dncore.User'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/' LOGIN_URL = '/user/login/'

View File

@ -17,25 +17,32 @@ class DomainForm(MntFormMixin, forms.ModelForm):
super(DomainForm, self).__init__(*args, **kwargs) super(DomainForm, self).__init__(*args, **kwargs)
instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk)
if not self._create:
self.fields['name'].disabled = True
def clean_name(self): def clean_name(self):
name = self.cleaned_data['name'].lower() name = self.cleaned_data['name'].lower()
if not name.endswith("."): if self._create:
name += "." if not name.endswith("."):
name += "."
if not name.endswith("dn."): if not name.endswith("dn."):
raise forms.ValidationError("Only .dn domains can be registered at this point") raise forms.ValidationError("Only .dn domains can be registered at this point")
if name.count(".") > 2: if name.count(".") > 2:
raise forms.ValidationError("No subdomains can be registered") raise forms.ValidationError("No subdomains can be registered")
if not re.match("^[a-z0-9.-]+$", name): if not re.match("^[a-z0-9.-]+$", name):
raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name") raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
try: try:
Domain.objects.get(name=name) Domain.objects.get(name=name)
raise forms.ValidationError("Domain already exists") raise forms.ValidationError("Domain already exists")
except Domain.DoesNotExist: except Domain.DoesNotExist:
pass pass
return name return name
@ -50,6 +57,9 @@ class NameserverForm(MntFormMixin, forms.ModelForm):
super(NameserverForm, self).__init__(*args, **kwargs) super(NameserverForm, self).__init__(*args, **kwargs)
instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk)
def clean_name(self): def clean_name(self):
name = self.cleaned_data['name'].lower().strip() name = self.cleaned_data['name'].lower().strip()
if not name.endswith("."): if not name.endswith("."):
@ -62,8 +72,14 @@ class NameserverForm(MntFormMixin, forms.ModelForm):
mnts = self._user.maintainer_set.all() mnts = self._user.maintainer_set.all()
domains = Domain.objects.filter(mnt_by__in=mnts) domains = Domain.objects.filter(mnt_by__in=mnts)
found = False
for domain in domains: for domain in domains:
if domain.endswith(name) if domain.name == zone:
found = True
break
if not found:
raise forms.ValidationError("This nameserver is not under a domain you control.")
return name return name

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-13 11:18 # Generated by Django 1.10.5 on 2017-03-14 21:04
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
@ -35,7 +35,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('last_modified', models.DateTimeField(auto_now_add=True)), ('last_modified', models.DateTimeField(auto_now_add=True)),
('name', models.CharField(max_length=256)), ('name', models.CharField(max_length=256, unique=True)),
('glueIPv4', models.GenericIPAddressField(blank=True, null=True, protocol='IPv4')), ('glueIPv4', models.GenericIPAddressField(blank=True, null=True, protocol='IPv4')),
('glueIPv6', models.GenericIPAddressField(blank=True, null=True, protocol='IPv6')), ('glueIPv6', models.GenericIPAddressField(blank=True, null=True, protocol='IPv6')),
('admin_c', models.ManyToManyField(to='whoisdb.Contact')), ('admin_c', models.ManyToManyField(to='whoisdb.Contact')),
@ -45,38 +45,16 @@ class Migration(migrations.Migration):
'abstract': False, 'abstract': False,
}, },
), ),
migrations.CreateModel(
name='NameserverDomainAssignment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveSmallIntegerField(default=0)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.Domain')),
('nameserver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.Nameserver')),
],
),
migrations.CreateModel(
name='NameserverReverseZoneAssignment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveSmallIntegerField()),
('nameserver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.Nameserver')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='ReverseZone', name='ReverseZone',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address', models.GenericIPAddressField(db_index=True)), ('address', models.GenericIPAddressField(db_index=True)),
('netmask', models.PositiveIntegerField()), ('netmask', models.PositiveIntegerField()),
('nameservers', models.ManyToManyField(through='domains.NameserverReverseZoneAssignment', to='domains.Nameserver')), ('nameservers', models.ManyToManyField(to='domains.Nameserver')),
('parentNet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='whoisdb.InetNum')), ('parentNet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='whoisdb.InetNum')),
], ],
), ),
migrations.AddField(
model_name='nameserverreversezoneassignment',
name='reversezone',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.ReverseZone'),
),
migrations.AddField( migrations.AddField(
model_name='domain', model_name='domain',
name='nameservers', name='nameservers',

View File

@ -6,6 +6,7 @@ from whoisdb.models import MntdObject, Contact, InetNum
# generally allow domains for .dn to be created # generally allow domains for .dn to be created
# allow owners of a subnet to create reverse dns record? # allow owners of a subnet to create reverse dns record?
class Nameserver(MntdObject): class Nameserver(MntdObject):
handleSuffix = "NS" handleSuffix = "NS"
# dns name # dns name
@ -24,6 +25,7 @@ class Nameserver(MntdObject):
def __str__(self): def __str__(self):
return self.name return self.name
class Domain(MntdObject): class Domain(MntdObject):
handle = None handle = None
handleSuffix = "DOM" handleSuffix = "DOM"
@ -38,18 +40,29 @@ class Domain(MntdObject):
def __str__(self): def __str__(self):
return self.name return self.name
def getNoDeleteReasons(self):
reasons = []
nameservers = Nameserver.objects.filter(name__endswith="." + self.name)
for ns in nameservers:
reasons.append("Nameserver %s depends on this domain" % ns.name)
return reasons
#class NameserverDomainAssignment(models.Model): #class NameserverDomainAssignment(models.Model):
# domain = models.ForeignKey(Domain) # domain = models.ForeignKey(Domain)
# nameserver = models.ForeignKey(Nameserver) # nameserver = models.ForeignKey(Nameserver)
# #
# order = models.PositiveSmallIntegerField(default=0) # order = models.PositiveSmallIntegerField(default=0)
class ReverseZone(models.Model): class ReverseZone(models.Model):
parentNet = models.ForeignKey(InetNum) parentNet = models.ForeignKey(InetNum)
address = models.GenericIPAddressField(db_index=True) address = models.GenericIPAddressField(db_index=True)
netmask = models.PositiveIntegerField() netmask = models.PositiveIntegerField()
nameservers = models.ManyToManyField(Nameserver, through='NameserverReverseZoneAssignment') nameservers = models.ManyToManyField(Nameserver)
#class NameserverReverseZoneAssignment(models.Model): #class NameserverReverseZoneAssignment(models.Model):
# reversezone = models.ForeignKey(ReverseZone) # reversezone = models.ForeignKey(ReverseZone)

View File

@ -6,8 +6,12 @@ urlpatterns = [
url(r'^$', domains_views.overview, name='overview'), url(r'^$', domains_views.overview, name='overview'),
url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'), url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'),
url(r'show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail, name='domain-show'), url(r'domain/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail.as_view(), name='domain-show'),
url(r'domain/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainEdit.as_view(), name='domain-edit'),
url(r'domain/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDelete.as_view(), name='domain-delete'),
url(r'nameserver/create/$', domains_views.NameserverCreate.as_view(), name='nameserver-create'), url(r'nameserver/create/$', domains_views.NameserverCreate.as_view(), name='nameserver-create'),
url(r'nameserver/show/(?P<pk>\d+)/$', domains_views.NameserverDetail, name='nameserver-show'), url(r'nameserver/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.NameserverDetail.as_view(), name='nameserver-show'),
url(r'nameserver/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.NameserverEdit.as_view(), name='nameserver-edit'),
url(r'nameserver/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.NameserverDelete.as_view(), name='nameserver-delete'),
] ]

View File

@ -1,13 +1,15 @@
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.generic import DetailView, CreateView, UpdateView from django.views.generic import DetailView, CreateView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from whoisdb.generic import MntGenericMixin from whoisdb.generic import MntGenericMixin, DeleteCheckView
from .models import Domain, Nameserver from .models import Domain, Nameserver
from .forms import DomainForm, NameserverForm from .forms import DomainForm, NameserverForm
@login_required @login_required
def overview(request): def overview(request):
mnts = request.user.maintainer_set.all() mnts = request.user.maintainer_set.all()
@ -18,6 +20,7 @@ def overview(request):
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers}) return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers})
class DomainCreate(LoginRequiredMixin, CreateView): class DomainCreate(LoginRequiredMixin, CreateView):
template_name = "domains/obj_create.html" template_name = "domains/obj_create.html"
form_class = DomainForm form_class = DomainForm
@ -28,13 +31,33 @@ class DomainCreate(LoginRequiredMixin, CreateView):
return kwargs return kwargs
class DomainDetail(LoginRequiredMixin, DetailView): class DomainDetail(LoginRequiredMixin, DetailView):
model = Domain model = Domain
slug_field = "name" slug_field = "name"
slug_url_kwarg = "domain" slug_url_kwarg = "domain"
context_object_name = "domain"
class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView): class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "domain" model = Domain
form_class = DomainForm
slug_field = "name"
slug_url_kwarg = "domain"
template_name = "domains/obj_edit.html"
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(DomainEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user
return kwargs
class DomainDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "domains/obj_delete.html"
model = Domain
slug_field = "name"
slug_url_kwarg = "domain"
success_url = reverse_lazy("domains:overview")
class NameserverCreate(LoginRequiredMixin, CreateView): class NameserverCreate(LoginRequiredMixin, CreateView):
@ -47,8 +70,30 @@ class NameserverCreate(LoginRequiredMixin, CreateView):
return kwargs return kwargs
class NameserverDetail(LoginRequiredMixin, DetailView): class NameserverDetail(LoginRequiredMixin, DetailView):
model = Nameserver model = Nameserver
#slug_field = "name" slug_field = "name"
#slug_url_kwarg = "domain" slug_url_kwarg = "domain"
context_object_name = "nameserver"
class NameserverEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
model = Nameserver
form_class = NameserverForm
slug_field = "name"
slug_url_kwarg = "domain"
template_name = "domains/obj_edit.html"
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(NameserverEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user
return kwargs
class NameserverDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
template_name = "domains/obj_delete.html"
model = Nameserver
slug_field = "name"
slug_url_kwarg = "domain"
success_url = reverse_lazy("domains:overview")

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Header</div>
<div class="panel-body">
{{ domain.name }}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Header</div>
<div class="panel-body">
{{ nameserver.name }}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-{% if reasons %}danger{%else%}default{%endif%}">
<div class="panel-heading">Header</div>
<div class="panel-body">
{% if reasons %}
<p>
You cannot delete this object, as other objects in the database depend on it!
</p>
<p>
<ul>
{% for reason in reasons %}
<li>{{ reason }}</li>
{% endfor %}
</ul>
{% else %}
{{ obj }}
<form method="post" action="#">
{% csrf_token %}
<button type="submit" class="btn btn-primary">Delete</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Header</div>
<div class="panel-body">
<form method="post" action="#">
{% csrf_token %}
{{ form | crispy }}
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -8,15 +8,46 @@
<div class="panel-body"> <div class="panel-body">
<p> <p>
Your nameservers (<a href="{% url "domains:nameserver-create" %}">New nameserver</a>) Your nameservers (<a href="{% url "domains:nameserver-create" %}">New nameserver</a>)
<table class="table">
<tr>
<th>Nameserver</th>
<th>Glue IPv4</th>
<th>Glue IPv6</th>
<th>MNTs</th>
<th></th>
</tr>
{% for nameserver in nameservers %} {% for nameserver in nameservers %}
{{ nameserver }}<br /> <tr>
<td><a href="{% url "domains:nameserver-show" nameserver.name %}">{{ nameserver.name }}</a></td>
<td></td>
<td></td>
<td></td>
<td><a href="{% url "domains:nameserver-edit" nameserver.name %}">Edit</a> <a href="{% url "domains:nameserver-delete" nameserver.name %}">Delete</a></td>
</tr>
{% endfor %} {% endfor %}
</table>
</p> </p>
<p> <p>
Your domains (<a href="{% url "domains:domain-create" %}">New domain</a>) Your domains (<a href="{% url "domains:domain-create" %}">New domain</a>)
<table class="table">
<tr>
<th>Domain</th>
<th>Nameserver</th>
<th>Glue IPv6</th>
<th>MNTs</th>
<th></th>
</tr>
{% for domain in domains %} {% for domain in domains %}
<tr>
<td><a href="{% url "domains:domain-show" domain.name %}">{{ domain.name }}</a></td>
<td></td>
<td></td>
<td></td>
<td><a href="{% url "domains:domain-edit" domain.name %}">Edit</a> <a href="{% url "domains:domain-delete" domain.name %}">Delete</a></td>
</tr>
{{ domain }}<br /> {{ domain }}<br />
{% endfor %} {% endfor %}
</table>
</div> </div>
</div> </div>
</div> </div>