From 69abba4d36bc25eb231bdb036d6bcd8f9454ca2a Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Tue, 14 Mar 2017 20:18:57 +0100 Subject: [PATCH] Zwischencommit --- README | 12 +++ dnmgmt/settings.py | 3 +- dnmgmt/urls.py | 4 + domains/__init__.py | 0 domains/admin.py | 7 ++ domains/apps.py | 5 + domains/forms.py | 73 +++++++++++++++ domains/migrations/0001_initial.py | 85 +++++++++++++++++ domains/migrations/__init__.py | 0 domains/models.py | 58 ++++++++++++ domains/tests.py | 3 + domains/urls.py | 13 +++ domains/views.py | 54 +++++++++++ rrequests/__init__.py | 0 rrequests/admin.py | 6 ++ rrequests/apps.py | 5 + rrequests/forms.py | 55 +++++++++++ rrequests/migrations/0001_initial.py | 39 ++++++++ .../migrations/0002_auto_20170305_0436.py | 45 +++++++++ .../migrations/0003_requestmessage_request.py | 22 +++++ .../0004_requestmessage_statuschanged.py | 20 ++++ .../migrations/0005_auto_20170306_1153.py | 30 ++++++ rrequests/migrations/__init__.py | 0 rrequests/models.py | 44 +++++++++ rrequests/tests.py | 3 + rrequests/urls.py | 10 ++ rrequests/views.py | 91 +++++++++++++++++++ templates/base.html | 1 + templates/domains/obj_create.html | 21 +++++ templates/domains/overview.html | 25 +++++ templates/rrequests/list.html | 29 ++++++ templates/rrequests/request_create.html | 21 +++++ templates/rrequests/request_detail.html | 33 +++++++ templates/whoisdb/overview.html | 2 +- whoisdb/forms.py | 63 +++++++------ whoisdb/migrations/0013_auto_20170303_1206.py | 30 ++++++ whoisdb/models.py | 5 +- whoisdb/validators.py | 1 - whoisdb/views.py | 3 +- 39 files changed, 885 insertions(+), 36 deletions(-) create mode 100644 domains/__init__.py create mode 100644 domains/admin.py create mode 100644 domains/apps.py create mode 100644 domains/forms.py create mode 100644 domains/migrations/0001_initial.py create mode 100644 domains/migrations/__init__.py create mode 100644 domains/models.py create mode 100644 domains/tests.py create mode 100644 domains/urls.py create mode 100644 domains/views.py create mode 100644 rrequests/__init__.py create mode 100644 rrequests/admin.py create mode 100644 rrequests/apps.py create mode 100644 rrequests/forms.py create mode 100644 rrequests/migrations/0001_initial.py create mode 100644 rrequests/migrations/0002_auto_20170305_0436.py create mode 100644 rrequests/migrations/0003_requestmessage_request.py create mode 100644 rrequests/migrations/0004_requestmessage_statuschanged.py create mode 100644 rrequests/migrations/0005_auto_20170306_1153.py create mode 100644 rrequests/migrations/__init__.py create mode 100644 rrequests/models.py create mode 100644 rrequests/tests.py create mode 100644 rrequests/urls.py create mode 100644 rrequests/views.py create mode 100644 templates/domains/obj_create.html create mode 100644 templates/domains/overview.html create mode 100644 templates/rrequests/list.html create mode 100644 templates/rrequests/request_create.html create mode 100644 templates/rrequests/request_detail.html create mode 100644 whoisdb/migrations/0013_auto_20170303_1206.py diff --git a/README b/README index 3fdea80..7e76f9c 100644 --- a/README +++ b/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? diff --git a/dnmgmt/settings.py b/dnmgmt/settings.py index e27e9f1..80fc2ed 100644 --- a/dnmgmt/settings.py +++ b/dnmgmt/settings.py @@ -39,9 +39,10 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'crispy_forms', - 'taggit', 'dncore', 'whoisdb', + 'rrequests', + 'domains', ] MIDDLEWARE = [ diff --git a/dnmgmt/urls.py b/dnmgmt/urls.py index dac152e..01e23d4 100644 --- a/dnmgmt/urls.py +++ b/dnmgmt/urls.py @@ -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')), ] diff --git a/domains/__init__.py b/domains/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/domains/admin.py b/domains/admin.py new file mode 100644 index 0000000..afef450 --- /dev/null +++ b/domains/admin.py @@ -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) diff --git a/domains/apps.py b/domains/apps.py new file mode 100644 index 0000000..7b95ea2 --- /dev/null +++ b/domains/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DomainsConfig(AppConfig): + name = 'domains' diff --git a/domains/forms.py b/domains/forms.py new file mode 100644 index 0000000..0936e01 --- /dev/null +++ b/domains/forms.py @@ -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 diff --git a/domains/migrations/0001_initial.py b/domains/migrations/0001_initial.py new file mode 100644 index 0000000..c2705cd --- /dev/null +++ b/domains/migrations/0001_initial.py @@ -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'), + ), + ] diff --git a/domains/migrations/__init__.py b/domains/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/domains/models.py b/domains/models.py new file mode 100644 index 0000000..1fe65b5 --- /dev/null +++ b/domains/models.py @@ -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() diff --git a/domains/tests.py b/domains/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/domains/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/domains/urls.py b/domains/urls.py new file mode 100644 index 0000000..b31bf0b --- /dev/null +++ b/domains/urls.py @@ -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[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\d+)/$', domains_views.NameserverDetail, name='nameserver-show'), +] diff --git a/domains/views.py b/domains/views.py new file mode 100644 index 0000000..aaffc39 --- /dev/null +++ b/domains/views.py @@ -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" + diff --git a/rrequests/__init__.py b/rrequests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rrequests/admin.py b/rrequests/admin.py new file mode 100644 index 0000000..1a88c5a --- /dev/null +++ b/rrequests/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from .models import Request, RequestMessage + +admin.site.register(Request) +admin.site.register(RequestMessage) diff --git a/rrequests/apps.py b/rrequests/apps.py new file mode 100644 index 0000000..1f52832 --- /dev/null +++ b/rrequests/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class RrequestsConfig(AppConfig): + name = 'rrequests' diff --git a/rrequests/forms.py b/rrequests/forms.py new file mode 100644 index 0000000..00b1528 --- /dev/null +++ b/rrequests/forms.py @@ -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) diff --git a/rrequests/migrations/0001_initial.py b/rrequests/migrations/0001_initial.py new file mode 100644 index 0000000..c8355db --- /dev/null +++ b/rrequests/migrations/0001_initial.py @@ -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()), + ], + ), + ] diff --git a/rrequests/migrations/0002_auto_20170305_0436.py b/rrequests/migrations/0002_auto_20170305_0436.py new file mode 100644 index 0000000..8fe7e35 --- /dev/null +++ b/rrequests/migrations/0002_auto_20170305_0436.py @@ -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, + ), + ] diff --git a/rrequests/migrations/0003_requestmessage_request.py b/rrequests/migrations/0003_requestmessage_request.py new file mode 100644 index 0000000..4bf4be1 --- /dev/null +++ b/rrequests/migrations/0003_requestmessage_request.py @@ -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, + ), + ] diff --git a/rrequests/migrations/0004_requestmessage_statuschanged.py b/rrequests/migrations/0004_requestmessage_statuschanged.py new file mode 100644 index 0000000..ae890a0 --- /dev/null +++ b/rrequests/migrations/0004_requestmessage_statuschanged.py @@ -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), + ), + ] diff --git a/rrequests/migrations/0005_auto_20170306_1153.py b/rrequests/migrations/0005_auto_20170306_1153.py new file mode 100644 index 0000000..6fb8a49 --- /dev/null +++ b/rrequests/migrations/0005_auto_20170306_1153.py @@ -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), + ), + ] diff --git a/rrequests/migrations/__init__.py b/rrequests/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rrequests/models.py b/rrequests/models.py new file mode 100644 index 0000000..7af31b0 --- /dev/null +++ b/rrequests/models.py @@ -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) diff --git a/rrequests/tests.py b/rrequests/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/rrequests/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/rrequests/urls.py b/rrequests/urls.py new file mode 100644 index 0000000..1dfd0e6 --- /dev/null +++ b/rrequests/urls.py @@ -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\d+)/$', rrequests_views.rrequestDetail, name='show'), +] diff --git a/rrequests/views.py b/rrequests/views.py new file mode 100644 index 0000000..4d55df9 --- /dev/null +++ b/rrequests/views.py @@ -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}) + diff --git a/templates/base.html b/templates/base.html index dbcfd0c..86353dd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -53,6 +53,7 @@ diff --git a/templates/domains/obj_create.html b/templates/domains/obj_create.html new file mode 100644 index 0000000..691f4df --- /dev/null +++ b/templates/domains/obj_create.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+
+
+
Header
+
+
+ {% csrf_token %} + {{ form | crispy }} + +
+
+
+
+
+{% endblock %} + diff --git a/templates/domains/overview.html b/templates/domains/overview.html new file mode 100644 index 0000000..8f796cd --- /dev/null +++ b/templates/domains/overview.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
Resource Requests
+
+

+ Your nameservers (New nameserver) + {% for nameserver in nameservers %} + {{ nameserver }}
+ {% endfor %} +

+

+ Your domains (New domain) + {% for domain in domains %} + {{ domain }}
+ {% endfor %} +

+
+
+
+{% endblock %} + diff --git a/templates/rrequests/list.html b/templates/rrequests/list.html new file mode 100644 index 0000000..2f550c0 --- /dev/null +++ b/templates/rrequests/list.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
Resource Requests
+
+

+ Create resource request +

+

+ Requests to you +

    + {% for r in requestedFromMe %} + {% endfor %} +
+ +

+
+
+
+
+{% endblock %} + diff --git a/templates/rrequests/request_create.html b/templates/rrequests/request_create.html new file mode 100644 index 0000000..691f4df --- /dev/null +++ b/templates/rrequests/request_create.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+
+
+
Header
+
+
+ {% csrf_token %} + {{ form | crispy }} + +
+
+
+
+
+{% endblock %} + diff --git a/templates/rrequests/request_detail.html b/templates/rrequests/request_detail.html new file mode 100644 index 0000000..0c61cdb --- /dev/null +++ b/templates/rrequests/request_detail.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+
+
+
Header
+
+

{{ request.subject }}

+

+ {% for message in request.requestmessage_set.all %} +

{{ message.creator }} {{ message.created }}

+ {{ message.message | linebreaks }} + + {% if message.statusChanged %} + Status changed to {{ message.statusChanged }}. + {% endif %} + {% endfor %} +

+

Add / change

+
+ {% csrf_token %} + {{ form | crispy }} + +
+
+
+
+
+{% endblock %} + diff --git a/templates/whoisdb/overview.html b/templates/whoisdb/overview.html index 587e948..d218d42 100644 --- a/templates/whoisdb/overview.html +++ b/templates/whoisdb/overview.html @@ -12,7 +12,7 @@
  • Create new Maintainer
  • Create new Role/Person
  • -
  • Request resources
  • +
  • Request resources
  • {% if netblocks %}
  • Create Subnet
  • {% endif %} diff --git a/whoisdb/forms.py b/whoisdb/forms.py index a21ce7f..39a83b5 100644 --- a/whoisdb/forms.py +++ b/whoisdb/forms.py @@ -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: diff --git a/whoisdb/migrations/0013_auto_20170303_1206.py b/whoisdb/migrations/0013_auto_20170303_1206.py new file mode 100644 index 0000000..aabfc70 --- /dev/null +++ b/whoisdb/migrations/0013_auto_20170303_1206.py @@ -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), + ), + ] diff --git a/whoisdb/models.py b/whoisdb/models.py index b9864b4..d213d83 100644 --- a/whoisdb/models.py +++ b/whoisdb/models.py @@ -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}) diff --git a/whoisdb/validators.py b/whoisdb/validators.py index e3a5479..71d0fb4 100644 --- a/whoisdb/validators.py +++ b/whoisdb/validators.py @@ -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 ) diff --git a/whoisdb/views.py b/whoisdb/views.py index 825b8a7..221a24b 100644 --- a/whoisdb/views.py +++ b/whoisdb/views.py @@ -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"] = {