Zwischencommit
This commit is contained in:
parent
3826078d75
commit
69abba4d36
12
README
12
README
|
@ -7,3 +7,15 @@ FIXME
|
|||
contact soll nur einen mnt_by haben
|
||||
mnt_by ==> mnt_by_in in filter statements
|
||||
admin_c für welche objekte?
|
||||
|
||||
mnt_by von Contact auf ForeignKey droppen?
|
||||
last-changed has to be changed by forms
|
||||
|
||||
|
||||
Visibility of resources
|
||||
MNT: Everywhere
|
||||
ASBlocks: ASBlock, ASNumber
|
||||
|
||||
|
||||
FIXME: Allow Maintainers to have multiple auth foo
|
||||
multiple select without revealing everything?
|
||||
|
|
|
@ -39,9 +39,10 @@ INSTALLED_APPS = [
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'crispy_forms',
|
||||
'taggit',
|
||||
'dncore',
|
||||
'whoisdb',
|
||||
'rrequests',
|
||||
'domains',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
|
|
@ -19,6 +19,8 @@ from django.contrib import admin
|
|||
import dncore.urls
|
||||
import dncore.views
|
||||
import whoisdb.urls
|
||||
import rrequests.urls
|
||||
import domains.urls
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -28,4 +30,6 @@ urlpatterns = [
|
|||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^user/', include(dncore.urls, namespace='user')),
|
||||
url(r'^whoisdb/', include(whoisdb.urls, namespace='whoisdb')),
|
||||
url(r'^rrequests/', include(rrequests.urls, namespace='rrequests')),
|
||||
url(r'^domains/', include(domains.urls, namespace='domains')),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Nameserver, Domain, ReverseZone
|
||||
|
||||
admin.site.register(Nameserver)
|
||||
admin.site.register(Domain)
|
||||
admin.site.register(ReverseZone)
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DomainsConfig(AppConfig):
|
||||
name = 'domains'
|
|
@ -0,0 +1,73 @@
|
|||
from django import forms
|
||||
|
||||
from whoisdb.forms import MntFormMixin
|
||||
|
||||
from .models import Domain, Nameserver
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class DomainForm(MntFormMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ['name', 'nameservers', 'mnt_by', 'admin_c']
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self._user = user
|
||||
|
||||
super(DomainForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name'].lower()
|
||||
if not name.endswith("."):
|
||||
name += "."
|
||||
|
||||
if not name.endswith("dn."):
|
||||
raise forms.ValidationError("Only .dn domains can be registered at this point")
|
||||
|
||||
if name.count(".") > 2:
|
||||
raise forms.ValidationError("No subdomains can be registered")
|
||||
|
||||
if not re.match("^[a-z0-9.-]+$", name):
|
||||
raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
|
||||
|
||||
try:
|
||||
Domain.objects.get(name=name)
|
||||
raise forms.ValidationError("Domain already exists")
|
||||
except Domain.DoesNotExist:
|
||||
pass
|
||||
|
||||
return name
|
||||
|
||||
|
||||
class NameserverForm(MntFormMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = Nameserver
|
||||
fields = ['name', 'glueIPv4', 'glueIPv6', 'mnt_by', 'admin_c']
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self._user = user
|
||||
|
||||
super(NameserverForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name'].lower().strip()
|
||||
if not name.endswith("."):
|
||||
name += "."
|
||||
|
||||
if name.count(".") <= 2:
|
||||
raise forms.ValidationError("Nameserver must be inside a domain (e.g. ns1.noot.dn.)")
|
||||
|
||||
zone = ".".join(name.split(".")[-3:])
|
||||
|
||||
mnts = self._user.maintainer_set.all()
|
||||
domains = Domain.objects.filter(mnt_by__in=mnts)
|
||||
for domain in domains:
|
||||
if domain.endswith(name)
|
||||
|
||||
return name
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(NameserverForm, self).clean()
|
||||
|
||||
return cleaned_data
|
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-13 11:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('whoisdb', '0013_auto_20170303_1206'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_modified', models.DateTimeField(auto_now_add=True)),
|
||||
('name', models.CharField(db_index=True, max_length=67, unique=True)),
|
||||
('admin_c', models.ManyToManyField(to='whoisdb.Contact')),
|
||||
('mnt_by', models.ManyToManyField(to='whoisdb.Maintainer')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Nameserver',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_modified', models.DateTimeField(auto_now_add=True)),
|
||||
('name', models.CharField(max_length=256)),
|
||||
('glueIPv4', models.GenericIPAddressField(blank=True, null=True, protocol='IPv4')),
|
||||
('glueIPv6', models.GenericIPAddressField(blank=True, null=True, protocol='IPv6')),
|
||||
('admin_c', models.ManyToManyField(to='whoisdb.Contact')),
|
||||
('mnt_by', models.ManyToManyField(to='whoisdb.Maintainer')),
|
||||
],
|
||||
options={
|
||||
'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(
|
||||
name='ReverseZone',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('address', models.GenericIPAddressField(db_index=True)),
|
||||
('netmask', models.PositiveIntegerField()),
|
||||
('nameservers', models.ManyToManyField(through='domains.NameserverReverseZoneAssignment', to='domains.Nameserver')),
|
||||
('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(
|
||||
model_name='domain',
|
||||
name='nameservers',
|
||||
field=models.ManyToManyField(blank=True, to='domains.Nameserver'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,58 @@
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from whoisdb.models import MntdObject, Contact, InetNum
|
||||
|
||||
# generally allow domains for .dn to be created
|
||||
# allow owners of a subnet to create reverse dns record?
|
||||
|
||||
class Nameserver(MntdObject):
|
||||
handleSuffix = "NS"
|
||||
# dns name
|
||||
# ip address, if glue
|
||||
# ipv4/ipv6 address?
|
||||
handle = None
|
||||
name = models.CharField(max_length=256, unique=True)
|
||||
glueIPv4 = models.GenericIPAddressField(protocol='IPv4', blank=True, null=True)
|
||||
glueIPv6 = models.GenericIPAddressField(protocol='IPv6', blank=True, null=True)
|
||||
|
||||
admin_c = models.ManyToManyField(Contact)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("domains:nameserver-show", args=(self.name,))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Domain(MntdObject):
|
||||
handle = None
|
||||
handleSuffix = "DOM"
|
||||
name = models.CharField(max_length=67, unique=True, db_index=True)
|
||||
|
||||
nameservers = models.ManyToManyField(Nameserver, blank=True)
|
||||
admin_c = models.ManyToManyField(Contact)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("domains:domain-show", args=(self.name,))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
#class NameserverDomainAssignment(models.Model):
|
||||
# domain = models.ForeignKey(Domain)
|
||||
# nameserver = models.ForeignKey(Nameserver)
|
||||
#
|
||||
# order = models.PositiveSmallIntegerField(default=0)
|
||||
|
||||
class ReverseZone(models.Model):
|
||||
parentNet = models.ForeignKey(InetNum)
|
||||
address = models.GenericIPAddressField(db_index=True)
|
||||
netmask = models.PositiveIntegerField()
|
||||
|
||||
nameservers = models.ManyToManyField(Nameserver, through='NameserverReverseZoneAssignment')
|
||||
|
||||
#class NameserverReverseZoneAssignment(models.Model):
|
||||
# reversezone = models.ForeignKey(ReverseZone)
|
||||
# nameserver = models.ForeignKey(Nameserver)
|
||||
#
|
||||
# order = models.PositiveSmallIntegerField()
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,13 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views as domains_views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', domains_views.overview, name='overview'),
|
||||
|
||||
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'nameserver/create/$', domains_views.NameserverCreate.as_view(), name='nameserver-create'),
|
||||
url(r'nameserver/show/(?P<pk>\d+)/$', domains_views.NameserverDetail, name='nameserver-show'),
|
||||
]
|
|
@ -0,0 +1,54 @@
|
|||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.generic import DetailView, CreateView, UpdateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from whoisdb.generic import MntGenericMixin
|
||||
|
||||
from .models import Domain, Nameserver
|
||||
from .forms import DomainForm, NameserverForm
|
||||
|
||||
@login_required
|
||||
def overview(request):
|
||||
mnts = request.user.maintainer_set.all()
|
||||
|
||||
# get all domains and nameservers
|
||||
domains = Domain.objects.filter(mnt_by__in=mnts)
|
||||
nameservers = Nameserver.objects.filter(mnt_by__in=mnts)
|
||||
|
||||
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers})
|
||||
|
||||
class DomainCreate(LoginRequiredMixin, CreateView):
|
||||
template_name = "domains/obj_create.html"
|
||||
form_class = DomainForm
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
kwargs = super(DomainCreate, self).get_form_kwargs(*args, **kwargs)
|
||||
kwargs["user"] = self.request.user
|
||||
|
||||
return kwargs
|
||||
|
||||
class DomainDetail(LoginRequiredMixin, DetailView):
|
||||
model = Domain
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "domain"
|
||||
|
||||
class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
|
||||
template_name = "domain"
|
||||
|
||||
|
||||
class NameserverCreate(LoginRequiredMixin, CreateView):
|
||||
template_name = "domains/obj_create.html"
|
||||
form_class = NameserverForm
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
kwargs = super(NameserverCreate, self).get_form_kwargs(*args, **kwargs)
|
||||
kwargs["user"] = self.request.user
|
||||
|
||||
return kwargs
|
||||
|
||||
class NameserverDetail(LoginRequiredMixin, DetailView):
|
||||
model = Nameserver
|
||||
#slug_field = "name"
|
||||
#slug_url_kwarg = "domain"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Request, RequestMessage
|
||||
|
||||
admin.site.register(Request)
|
||||
admin.site.register(RequestMessage)
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RrequestsConfig(AppConfig):
|
||||
name = 'rrequests'
|
|
@ -0,0 +1,55 @@
|
|||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import Request
|
||||
from whoisdb.models import Maintainer
|
||||
|
||||
class RequestForm(forms.Form):
|
||||
RESOURCES = map(lambda _x: (_x, _x), [
|
||||
"AS Number (16bit)", "IPv4 /27", "IPv4 > /27", "IPv6", "other"
|
||||
])
|
||||
|
||||
applicant = forms.ModelChoiceField(Maintainer.objects.none(), label="Applicant (you)", help_text="Maintainer you want to request resources for")
|
||||
provider = forms.ModelChoiceField(Maintainer.objects.none(), label="Provider", help_text="LIR/RIR you want to request resources from")
|
||||
|
||||
subject = forms.CharField(label="Subject")
|
||||
resources = forms.CheckboxSelectMultiple(choices=RESOURCES)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Describe shortly what resources you need and what for you need them")
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(RequestForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self._user = user
|
||||
self.fields['applicant'].queryset = self._user.maintainer_set.all()
|
||||
self.fields['provider'].queryset = Maintainer.objects.filter(Q(rir=True) | Q(lir=True))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(RequestForm, self).clean()
|
||||
|
||||
if not forms.errors:
|
||||
mnts = self._user.maintainer_set.all()
|
||||
if cleaned_data['applicant'] in mnts and cleaned_data['provider'] in mnts:
|
||||
raise forms.ValidationError("You could request resources from yourself, but this would actually not make that much sense.")
|
||||
|
||||
class ResponseForm(forms.Form):
|
||||
new_status = forms.ChoiceField(choices=[("KEEP", "----", )] + list(Request.STATES), initial="KEEP", help_text="Only set this if you want to change the status of this request")
|
||||
message = forms.CharField(widget=forms.Textarea)
|
||||
|
||||
def __init__(self, request, user, *args, **kwargs):
|
||||
super(ResponseForm, self).__init__(*args, **kwargs)
|
||||
self._request = request
|
||||
self._user = user
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ResponseForm, self).clean()
|
||||
|
||||
if not self.errors:
|
||||
if cleaned_data['new_status'] == self._request.status:
|
||||
raise forms.ValidationError("Status changed to same as ticket")
|
||||
if self._request.status in (Request.STATE_RESOLVED, Request.STATE_REJECTED) and \
|
||||
cleaned_data['new_status'] not in (Request.STATE_OPEN,):
|
||||
raise forms.ValidationError("Please put this ticket in an open state before adding messages")
|
||||
|
||||
|
||||
class ProviderResponseForm(ResponseForm):
|
||||
createdResources = forms.CharField(label="Created resources", help_text="If you have created resources for this request, please enter their handles here", required=False)
|
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-03 12:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('whoisdb', '0013_auto_20170303_1206'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Request',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('state', models.CharField(choices=[('Open', 'OPEN'), ('Resolved', 'RESOLVED'), ('Rejected', 'REJECTED')], max_length=16)),
|
||||
('requestResources', models.TextField()),
|
||||
('grantedResources', models.TextField()),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('lastAction', models.DateTimeField(auto_now_add=True)),
|
||||
('requestedFrom', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requestfrom_set', to='whoisdb.Maintainer')),
|
||||
('requestedTo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='whoisdb.Maintainer')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RequestMessage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('message', models.TextField()),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-05 04:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('whoisdb', '0013_auto_20170303_1206'),
|
||||
('rrequests', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='request',
|
||||
old_name='requestedTo',
|
||||
new_name='applicant',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='request',
|
||||
old_name='requestedFrom',
|
||||
new_name='provider',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='request',
|
||||
old_name='title',
|
||||
new_name='subject',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='requestmessage',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='requestmessage',
|
||||
name='creator',
|
||||
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='whoisdb.Maintainer'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-06 02:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rrequests', '0002_auto_20170305_0436'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='requestmessage',
|
||||
name='request',
|
||||
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='rrequests.Request'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-06 11:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rrequests', '0003_requestmessage_request'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='requestmessage',
|
||||
name='statusChanged',
|
||||
field=models.CharField(blank=True, choices=[('Open', 'OPEN'), ('Resolved', 'RESOLVED'), ('Rejected', 'REJECTED')], default=None, max_length=16, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-06 11:53
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rrequests', '0004_requestmessage_statuschanged'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='request',
|
||||
name='state',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='request',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('OPEN', 'Open'), ('RESOLVED', 'Resolved'), ('REJECTED', 'Rejected')], default='OPEN', max_length=16),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='requestmessage',
|
||||
name='statusChanged',
|
||||
field=models.CharField(blank=True, choices=[('OPEN', 'Open'), ('RESOLVED', 'Resolved'), ('REJECTED', 'Rejected')], default=None, max_length=16, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,44 @@
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from whoisdb.models import Maintainer
|
||||
|
||||
class Request(models.Model):
|
||||
STATE_OPEN = "OPEN"
|
||||
STATE_RESOLVED = "RESOLVED"
|
||||
STATE_REJECTED = "REJECTED"
|
||||
STATES = (
|
||||
(STATE_OPEN, 'Open'),
|
||||
(STATE_RESOLVED, 'Resolved'),
|
||||
(STATE_REJECTED, 'Rejected'),
|
||||
)
|
||||
|
||||
# request goes to mnt?
|
||||
subject = models.CharField(max_length=200)
|
||||
|
||||
status = models.CharField(max_length=16, choices=STATES)
|
||||
|
||||
applicant = models.ForeignKey(Maintainer)
|
||||
provider = models.ForeignKey(Maintainer, related_name='requestfrom_set')
|
||||
|
||||
requestResources = models.TextField()
|
||||
grantedResources = models.TextField()
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
lastAction = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("rrequests:show", args=(self.pk,))
|
||||
|
||||
def __str__(self):
|
||||
return "(%s -> %s) [%s] %s" % (self.applicant, self.provider, self.state, self.subject)
|
||||
|
||||
|
||||
class RequestMessage(models.Model):
|
||||
request = models.ForeignKey(Request)
|
||||
|
||||
statusChanged = models.CharField(max_length=16, choices=Request.STATES, default=None, null=True, blank=True)
|
||||
|
||||
creator = models.ForeignKey(Maintainer)
|
||||
message = models.TextField()
|
||||
created = models.DateTimeField(auto_now_add=True)
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,10 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views as rrequests_views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', rrequests_views.listRequests, name='dashboard'),
|
||||
|
||||
url(r'create/$', rrequests_views.RrequestCreate.as_view(), name='create'),
|
||||
url(r'show/(?P<pk>\d+)/$', rrequests_views.rrequestDetail, name='show'),
|
||||
]
|
|
@ -0,0 +1,91 @@
|
|||
from django.shortcuts import render, get_object_or_404
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
from django.views.generic import FormView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import Request, RequestMessage
|
||||
from .forms import RequestForm, ResponseForm, ProviderResponseForm
|
||||
|
||||
@login_required
|
||||
def listRequests(request):
|
||||
mnts = request.user.maintainer_set.all()
|
||||
requestedFromMe = Request.objects.filter(applicant=mnts)
|
||||
requestedToMe = Request.objects.filter(provider__in=mnts)
|
||||
|
||||
return render(request, "rrequests/list.html", {"requestedFromMe": requestedFromMe, "requestedToMe": requestedToMe})
|
||||
|
||||
|
||||
class RrequestCreate(LoginRequiredMixin, FormView):
|
||||
template_name = "rrequests/request_create.html"
|
||||
form_class = RequestForm
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
kwargs = super(RrequestCreate, self).get_form_kwargs(*args, **kwargs)
|
||||
kwargs['user'] = self.request.user
|
||||
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
formData = form.cleaned_data
|
||||
print(formData)
|
||||
request = Request(
|
||||
subject=formData['subject'],
|
||||
status=Request.STATE_OPEN,
|
||||
applicant=formData['applicant'],
|
||||
provider=formData['provider'],
|
||||
)
|
||||
request.save()
|
||||
|
||||
requestMsg = RequestMessage(
|
||||
request=request,
|
||||
|
||||
creator=formData['applicant'],
|
||||
message=formData['message'],
|
||||
)
|
||||
requestMsg.save()
|
||||
|
||||
return HttpResponseRedirect(request.get_absolute_url())
|
||||
|
||||
@login_required
|
||||
def rrequestDetail(request, pk):
|
||||
reqObj = get_object_or_404(Request, pk=pk)
|
||||
|
||||
mnts = request.user.maintainer_set.all()
|
||||
formClass = None
|
||||
provider = None
|
||||
if reqObj.provider in mnts:
|
||||
provider = True
|
||||
formClass = ProviderResponseForm
|
||||
else:
|
||||
provider = False
|
||||
formClass = ResponseForm
|
||||
|
||||
if request.method == "POST":
|
||||
form = formClass(request=reqObj, user=request.user, data=request.POST)
|
||||
if form.is_valid():
|
||||
# create message object
|
||||
msg = RequestMessage(
|
||||
request=reqObj,
|
||||
creator=reqObj.provider if provider else reqObj.applicant,
|
||||
message=form.cleaned_data['message']
|
||||
)
|
||||
if form.cleaned_data['new_status'] != "KEEP":
|
||||
msg.statusChanged = form.cleaned_data['new_status']
|
||||
reqObj.status = msg.statusChanged
|
||||
|
||||
msg.save()
|
||||
|
||||
reqObj.lastAction = timezone.now()
|
||||
reqObj.save()
|
||||
|
||||
|
||||
return HttpResponseRedirect(reverse("rrequests:show", args=(pk,)))
|
||||
else:
|
||||
form = formClass(request=reqObj, user=request.user)
|
||||
|
||||
return render(request, "rrequests/request_detail.html", {"request": reqObj, "form": form})
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Whois DB <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url "whoisdb:dashboard" %}">Overview</a></li>
|
||||
<li><a href="{% url "domains:overview" %}">Domains</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -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">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Resource Requests</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
Your nameservers (<a href="{% url "domains:nameserver-create" %}">New nameserver</a>)
|
||||
{% for nameserver in nameservers %}
|
||||
{{ nameserver }}<br />
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>
|
||||
Your domains (<a href="{% url "domains:domain-create" %}">New domain</a>)
|
||||
{% for domain in domains %}
|
||||
{{ domain }}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Resource Requests</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a href="{% url "rrequests:create" %}">Create resource request</a>
|
||||
</p>
|
||||
<p>
|
||||
Requests to you
|
||||
<ul>
|
||||
{% for r in requestedFromMe %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<ul>
|
||||
{% for r in requestedToMe %}
|
||||
<li><a href="{% url "rrequests:show" r.pk %}">{{ r.subject }} {{ r.created }} {{ r.applicant }} {{ r.provider }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -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">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -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-default">
|
||||
<div class="panel-heading">Header</div>
|
||||
<div class="panel-body">
|
||||
<h2>{{ request.subject }}</h2>
|
||||
<p>
|
||||
{% for message in request.requestmessage_set.all %}
|
||||
<h4>{{ message.creator }} {{ message.created }}</h4>
|
||||
{{ message.message | linebreaks }}
|
||||
|
||||
{% if message.statusChanged %}
|
||||
Status changed to {{ message.statusChanged }}.
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<h4>Add / change</h4>
|
||||
<form method="post" action="#">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<button class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<ul>
|
||||
<li><a href="{% url "whoisdb:mnt-create" %}">Create new Maintainer</a></li>
|
||||
<li><a href="{% url "whoisdb:contact-create" %}">Create new Role/Person</a></li>
|
||||
<li><a href="">Request resources</a></li>
|
||||
<li><a href="{% url "rrequests:dashboard" %}">Request resources</a></li>
|
||||
{% if netblocks %}
|
||||
<li><a href="{% url "whoisdb:inetnum-create" %}">Create Subnet</a></li>
|
||||
{% endif %}
|
||||
|
|
|
@ -7,9 +7,9 @@ import re
|
|||
import ipaddress
|
||||
|
||||
|
||||
class WhoisObjectMixin(object):
|
||||
class WhoisObjectFormMixin(object):
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(WhoisObjectMixin, self).__init__(*args, **kwargs)
|
||||
super(WhoisObjectFormMixin, self).__init__(*args, **kwargs)
|
||||
self._user = user
|
||||
|
||||
instance = getattr(self, 'instance', None)
|
||||
|
@ -30,7 +30,7 @@ class WhoisObjectMixin(object):
|
|||
return self.cleaned_data['handle']
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(WhoisObjectMixin, self).clean()
|
||||
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"))
|
||||
|
||||
|
@ -55,7 +55,6 @@ class MntFormMixin(object):
|
|||
mntWhens.append(When(handle__in=instance.mnt_by.all().values_list("handle"), then=1))
|
||||
mntQs = Maintainer.objects.annotate(card=Case(*mntWhens, default=0, output_field=IntegerField())).order_by("-card")
|
||||
self.fields["mnt_by"].queryset = mntQs
|
||||
print(mntQs.query)
|
||||
|
||||
class MntForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
@ -75,14 +74,16 @@ class MntInitialForm(MntForm):
|
|||
fields = ['handle', 'description']
|
||||
|
||||
|
||||
class ContactForm(WhoisObjectMixin, forms.ModelForm):
|
||||
class ContactForm(WhoisObjectFormMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = ['type', 'handle', 'name', 'mnt_by']
|
||||
fields = ['handle', 'name', 'mnt_by']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ContactForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user)
|
||||
|
||||
|
||||
class ContactInitialForm(ContactForm):
|
||||
class Meta:
|
||||
|
@ -90,7 +91,7 @@ class ContactInitialForm(ContactForm):
|
|||
fields = ['handle', 'name']
|
||||
|
||||
|
||||
class InetNumForm(MntFormMixin, WhoisObjectMixin, forms.ModelForm):
|
||||
class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
|
||||
prefix = forms.CharField()
|
||||
protectedFields = ['handle', 'protocol', 'parent_range', 'mnt_by', 'prefix']
|
||||
|
||||
|
@ -123,41 +124,44 @@ class InetNumForm(MntFormMixin, WhoisObjectMixin, forms.ModelForm):
|
|||
|
||||
return net
|
||||
|
||||
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 (self.instance and self.instance.pk) and not parent_range:
|
||||
raise forms.ValidationError("Parent range must be set")
|
||||
|
||||
def clean(self):
|
||||
# FIXME: Reset certain field sto instance:
|
||||
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')):
|
||||
|
||||
if not self._editLower:
|
||||
if not self.errors:
|
||||
prefix = cleaned_data['prefix']
|
||||
parent = cleaned_data['parent_range']
|
||||
parentNet = parent.getNetwork()
|
||||
if parent:
|
||||
parentNet = parent.getNetwork()
|
||||
|
||||
if cleaned_data['protocol'] != parent.protocol:
|
||||
raise forms.ValidationError("Protocol type for prefix must be same as parent network")
|
||||
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 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
|
||||
# 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)
|
||||
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
|
||||
|
||||
class ASBlockForm(MntFormMixin, WhoisObjectMixin, forms.ModelForm):
|
||||
class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
|
||||
protectedFields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'mnt_by']
|
||||
|
||||
# FIXME: Filter blocks
|
||||
|
@ -205,13 +209,12 @@ class ASBlockForm(MntFormMixin, WhoisObjectMixin, forms.ModelForm):
|
|||
|
||||
if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \
|
||||
asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
|
||||
print("AS OVERLAP TEST", parent.asBegin, parent.asEnd, asBegin, asEnd)
|
||||
raise forms.ValidationError("Block overlaps with block %s" % block.handle)
|
||||
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class ASNumberForm(MntFormMixin, WhoisObjectMixin, forms.ModelForm):
|
||||
class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
|
||||
protectedFields = ['handle', 'asblock', 'number', 'mnt_by']
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-03 12:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('whoisdb', '0012_auto_20170302_0314'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='maintainer',
|
||||
name='lir',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='maintainer',
|
||||
name='rir',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contact',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('person', 'PERSON')], default='PERSON', max_length=10),
|
||||
),
|
||||
]
|
|
@ -68,6 +68,9 @@ class Maintainer(WhoisObject):
|
|||
|
||||
admin_c = models.ManyToManyField("Contact")
|
||||
|
||||
rir = models.BooleanField(default=False)
|
||||
lir = models.BooleanField(default=False)
|
||||
|
||||
# autoInclude = models.BooleanField(default=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
|
@ -100,7 +103,7 @@ class Contact(MntdObject):
|
|||
TYPE = (('person', TYPE_PERSON),)
|
||||
|
||||
name = models.CharField(max_length=128)
|
||||
type = models.CharField(max_length=10, choices=TYPE)
|
||||
type = models.CharField(max_length=10, choices=TYPE, default=TYPE_PERSON)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("whoisdb:contact-detail", kwargs={"handle": self.handle})
|
||||
|
|
|
@ -21,7 +21,6 @@ class HandleValidatorWithSuffix(validators.RegexValidator):
|
|||
|
||||
def __init__(self, suffix):
|
||||
self.regex = r'^(?:[A-Z]+[0-9]+-%s|AUTO)' % re.escape(suffix)
|
||||
print(self.regex)
|
||||
self.message = _(
|
||||
'Enter a valid handle with suffix %s (all uppercase)' % suffix
|
||||
)
|
||||
|
|
|
@ -46,8 +46,6 @@ def dbDashboard(request):
|
|||
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')
|
||||
|
||||
print(asnumbers, asblocks, netblocks, mnts)
|
||||
|
||||
return render(request, "whoisdb/overview.html", {"mnts": mnts, "contacts": contacts, "mntForm": mntForm, "contactForm": contactForm, "netblocks": netblocks, "asblocks": asblocks, "asnumbers": asnumbers})
|
||||
|
||||
|
||||
|
@ -156,6 +154,7 @@ class InetNumCreate(LoginRequiredMixin, CreateView):
|
|||
form_class = InetNumForm
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
print("NOOOOOOOOOOOOOOOOOOOOOOOOOOOOT", args, kwargs)
|
||||
kwargs = super(InetNumCreate, self).get_form_kwargs(*args, **kwargs)
|
||||
kwargs["user"] = self.request.user
|
||||
kwargs["initial"] = {
|
||||
|
|
Loading…
Reference in New Issue