Good, good, queryset foo

This commit is contained in:
Sebastian Lohff 2017-03-21 02:36:07 +01:00
parent baa2dcc38b
commit 189ae23b4a
12 changed files with 303 additions and 58 deletions

View File

@ -1,10 +1,14 @@
from django import forms
from django.db.models import Q
from whoisdb.models import InetNum
from whoisdb.forms import MntFormMixin
from whoisdb.validators import IP46CIDRValidator
from .models import Domain, Nameserver
from .models import Domain, Nameserver, ReverseZone
import re
import ipaddress
class DomainForm(MntFormMixin, forms.ModelForm):
@ -17,11 +21,17 @@ class DomainForm(MntFormMixin, forms.ModelForm):
super(DomainForm, self).__init__(*args, **kwargs)
mnts = self._user.maintainer_set.all()
self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk)
if not self._create:
self.fields['name'].disabled = True
self.fields['nameservers'].queryset |= self.instance.nameservers.all()
self.fields['nameservers'].queryset.distinct()
def clean_name(self):
name = self.cleaned_data['name'].lower()
@ -87,3 +97,56 @@ class NameserverForm(MntFormMixin, forms.ModelForm):
cleaned_data = super(NameserverForm, self).clean()
return cleaned_data
class ReverseZoneForm(forms.ModelForm):
prefix = forms.CharField(validators=[IP46CIDRValidator])
class Meta:
model = ReverseZone
fields = ['parentNet', 'nameservers']
def __init__(self, user, *args, **kwargs):
self._user = user
super(ReverseZoneForm, self).__init__(*args, **kwargs)
instance = getattr(self, "instance", None)
self._create = not (instance and instance.pk)
mnts = self._user.maintainer_set.all()
self.fields['parentNet'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
if not self._create:
self.fields['prefix'].disabled = True
self.fields['nameservers'].queryset |= self.instance.nameservers.all()
self.fields['nameservers'].queryset.distinct()
def clean(self):
cleaned_data = super(ReverseZoneForm, self).clean()
if not self.errors:
if self._create:
net = ipaddress.ip_network(cleaned_data['prefix'])
parentNet = cleaned_data['parentNet'].getNetwork()
if net.network_address not in parentNet:
raise forms.ValidationError("Given prefix %s is not inside of parent netblock %s" % (net, parentNet))
# For now just check all the zones...
#zones = ReverseZone.objects.filter(parentNet=cleaned_data['parentNet'])
zones = ReverseZone.objects.all()
for zone in zones:
if net.network_address in zone.parentNet.getNetwork():
raise forms.ValidationError("Given prefix already has a reverse zone object associated to it: %s" % zone)
self.instance.address = str(net.network_address)
self.instance.netmask = net.prefixlen
return cleaned_data

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-14 21:04
# Generated by Django 1.10.5 on 2017-03-20 11:31
from __future__ import unicode_literals
from django.db import migrations, models

View File

@ -3,6 +3,8 @@ from django.urls import reverse
from whoisdb.models import MntdObject, Contact, InetNum
import ipaddress
# generally allow domains for .dn to be created
# allow owners of a subnet to create reverse dns record?
@ -49,12 +51,6 @@ class Domain(MntdObject):
return reasons
#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)
@ -63,9 +59,18 @@ class ReverseZone(models.Model):
nameservers = models.ManyToManyField(Nameserver)
def prefix(self):
""" Helper function, mainly used in templates """
return "%s/%s" % (self.address, self.netmask)
#class NameserverReverseZoneAssignment(models.Model):
# reversezone = models.ForeignKey(ReverseZone)
# nameserver = models.ForeignKey(Nameserver)
#
# order = models.PositiveSmallIntegerField()
def getNetwork(self):
return ipaddress.ip_network(self.prefix())
def get_absolute_url(self):
return reverse("domains:reversezone-show", args=(self.pk,))
def __str__(self):
return "%s @ %s" % (self.prefix(), self.parentNet)
def getNoDeleteReasons(self):
return []

View File

@ -14,4 +14,10 @@ urlpatterns = [
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'),
url(r'reversezone/create/$', domains_views.ReverseZoneCreate.as_view(), name='reversezone-create'),
url(r'reversezone/show/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneDetail.as_view(), name='reversezone-show'),
url(r'reversezone/edit/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneEdit.as_view(), name='reversezone-edit'),
url(r'reversezone/delete/(?P<pk>[0-9]+)/$', domains_views.ReverseZoneDelete.as_view(), name='reversezone-delete'),
]

View File

@ -3,11 +3,12 @@ from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.views.generic import DetailView, CreateView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from whoisdb.generic import MntGenericMixin, DeleteCheckView
from .models import Domain, Nameserver
from .forms import DomainForm, NameserverForm
from .models import Domain, Nameserver, ReverseZone
from .forms import DomainForm, NameserverForm, ReverseZoneForm
@login_required
@ -15,10 +16,11 @@ 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)
domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
nameservers = Nameserver.objects.filter(mnt_by__in=mnts).distinct()
reversezones = ReverseZone.objects.filter(parentNet__mnt_by__in=mnts).distinct()
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers})
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers, 'reversezones': reversezones})
class DomainCreate(LoginRequiredMixin, CreateView):
@ -38,6 +40,8 @@ class DomainDetail(LoginRequiredMixin, DetailView):
slug_url_kwarg = "domain"
context_object_name = "domain"
template_name = "domains/handle_show.html"
class DomainEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
model = Domain
@ -97,3 +101,55 @@ class NameserverDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
slug_field = "name"
slug_url_kwarg = "domain"
success_url = reverse_lazy("domains:overview")
class ReverseZoneCreate(LoginRequiredMixin, CreateView):
template_name = "domains/obj_create.html"
form_class = ReverseZoneForm
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ReverseZoneCreate, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user
return kwargs
class ReverseZoneDetail(LoginRequiredMixin, DetailView):
model = ReverseZone
slug_field = "name"
slug_url_kwarg = "domain"
context_object_name = "nameserver"
class ReverseZoneEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
model = ReverseZone
form_class = ReverseZoneForm
#slug_field = "name"
#slug_url_kwarg = "domain"
template_name = "domains/obj_edit.html"
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(ReverseZoneEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user
if not "initial" in kwargs:
kwargs["initial"] = {}
kwargs["initial"]["prefix"] = self.object.prefix()
return kwargs
def get_queryset(self):
mnts = self.request.user.maintainer_set.all()
return ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()
class ReverseZoneDelete(LoginRequiredMixin, DeleteCheckView):
template_name = "domains/obj_delete.html"
model = ReverseZone
success_url = reverse_lazy("domains:overview")
#slug_field = "name"
#slug_url_kwarg = "domain"
def get_queryset(self):
mnts = self.request.user.maintainer_set.all()
return ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()

View File

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% load handletags %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">{{ object }} <small>({{ object.getClassName }})</small></div>
<div class="panel-body">
<table class="table">
{% for field in object|getFields:user %}
<tr>
<td>{{ field.0 }}</td>
<td>{% if field.1.through %}{{ field.1.all|linkObjects|default:"-" }}{% else %}{{ field.1|default:"-" }}{% endif %}</td>
</tr>
{% endfor %}
{% if object|userCanEdit:user %}
<tr>
<td>Actions</td>
{% with "domains:"|add:object.getClassName|lower|add:"-edit" as editView %}
{% with "domains:"|add:object.getClassName|lower|add:"-delete" as deleteView %}
<td><a href="{% url editView object.name %}">Edit object<a/>, <a href="{% url deleteView object.name %}">Delete object</a></td>
{% endwith %}
{% endwith %}
</tr>
{% endif %}
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -6,7 +6,7 @@
<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-heading">Delete {{ object }}?</div>
<div class="panel-body">
{% if reasons %}
<p>
@ -19,10 +19,12 @@
{% endfor %}
</ul>
{% else %}
{{ obj }}
<p>
Are you sure you want to delete {{ object }}?
</p>
<form method="post" action="#">
{% csrf_token %}
<button type="submit" class="btn btn-primary">Delete</button>
<button type="submit" class="btn btn-primary">Delete {{ object }}</button>
</form>
{% endif %}
</div>

View File

@ -1,13 +1,14 @@
{% extends "base.html" %}
{% load handletags %}
{% 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-heading">DNS Overview</div>
<div class="panel-body">
<p>
Your nameservers (<a href="{% url "domains:nameserver-create" %}">New nameserver</a>)
<h4>Your Nameservers (<a href="{% url "domains:nameserver-create" %}">New nameserver</a>)</h4>
<table class="table">
<tr>
<th>Nameserver</th>
@ -19,35 +20,62 @@
{% for nameserver in nameservers %}
<tr>
<td><a href="{% url "domains:nameserver-show" nameserver.name %}">{{ nameserver.name }}</a></td>
<td></td>
<td></td>
<td></td>
<td>{{ nameserver.glueIPv4|default:"-" }}</td>
<td>{{ nameserver.glueIPv6|default:"-" }}</td>
<td>{{ nameserver.mnt_by.all|linkObjects }}</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 %}
</table>
</p>
<p>
Your domains (<a href="{% url "domains:domain-create" %}">New domain</a>)
<h4>Your Domains (<a href="{% url "domains:domain-create" %}">New domain</a>)</h4>
<table class="table">
<tr>
<th>Domain</th>
<th>Nameserver</th>
<th>Glue IPv6</th>
<th>MNTs</th>
<th></th>
</tr>
{% 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>
<ul>
{% for nameserver in domain.nameservers.all %}
<li>{{ nameserver|linkObject }}</li>
{% endfor %}
</ul>
</td>
<td>{{ domain.mnt_by.all|linkObjects }}</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 />
{% endfor %}
</table>
<h4>Your Reverse Zones (<a href="{% url "domains:reversezone-create" %}">New reverse zone</a>)</h4>
<table class="table">
<tr>
<th>Zone</th>
<th>Network</th>
<th>Nameserver</th>
<th></th>
</tr>
{% for reversezone in reversezones %}
<tr>
<td><a href="{% url "domains:reversezone-show" reversezone.pk %}">{{ reversezone.prefix }}</a></td>
<td></td>
<td>
<ul>
{% for nameserver in domain.nameservers.all %}
<li>{{ nameserver|linkObject }}</li>
{% endfor %}
</ul>
</td>
<td><a href="{% url "domains:reversezone-edit" reversezone.pk %}">Edit</a> <a href="{% url "domains:reversezone-delete" reversezone.pk %}">Delete</a></td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>

View File

@ -14,18 +14,19 @@ class WhoisObjectFormMixin(object):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self._create = False
self.fields['handle'].widget.attrs['readonly'] = True
else:
self._create = True
# only show users contacts and already present contacts
mnts = self._user.maintainer_set.all()
if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.objects.filter(mnt_by__in=self._user.maintainer_set.all())
if instance and instance.pk:
self.fields['admin_c'].queryset |= instance.admin_c.all()
self.fields['admin_c'].queryset = Contact.getMntQueryset(mnts, self.instance, "admin_c")
def clean_handle(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance.handle
if not self._create:
return self.instance.handle
else:
return self.cleaned_data['handle']
@ -56,17 +57,14 @@ class MntFormMixin(object):
mntQs = Maintainer.objects.annotate(card=Case(*mntWhens, default=0, output_field=IntegerField())).order_by("-card")
self.fields["mnt_by"].queryset = mntQs
class MntForm(forms.ModelForm):
if "mnt_lower" in self.fields:
self.fields["mnt_lower"].queryset = mntQs
class MntForm(WhoisObjectFormMixin, forms.ModelForm):
class Meta:
model = Maintainer
fields = ['handle', 'description', 'admin_c']
def __init__(self, user, *args, **kwargs):
super(MntForm, self).__init__(*args, **kwargs)
self._user = user
if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.objects.filter(mnt_by=user.maintainer_set.all())
class MntInitialForm(MntForm):
class Meta:
@ -82,7 +80,7 @@ class ContactForm(WhoisObjectFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user)
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
class ContactInitialForm(ContactForm):
@ -101,16 +99,18 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InetNumForm, self).__init__(*args, **kwargs)
if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.objects.filter(mnt_by__in=self.user.maintainer_set.all())
instance = getattr(self, "instance", None)
if instance and instance.pk:
self.fields['admin_c'].queryset |= instance.admin_c.all()
if self._editLower:
for key in self.protectedFields:
self.fields[key].disabled = True
self.fields[key].widget.attrs['readonly'] = False
mnts = self._user.maintainer_set.all()
#self.fields['parent_range'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts))
#if not self._create:
# self.fields['parent_range'].queryset |= InetNum.objects.filter(pk=self.instance.pk)
#self.fields['parent_range'].queryset = self.fields['parent_range'].queryset.distinct()
self.fields['parent_range'].queryset = InetNum.getMntQueryset(mnts, self.instance, "parent_range")
def clean_prefix(self):
# make sure this is a subnet we're getting
@ -128,9 +128,19 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
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:
if not parent_range and (self._create or not self._create and self.instance.parent_range):
raise forms.ValidationError("Parent range must be set")
if not self._create and parent_range:
# make sure we don't have circular dependencies
obj = parent_range
while obj.parent_range:
if obj.pk == self.instance.pk:
raise forms.ValidationError("No circular dependencies allowed")
obj = obj.parent_range
return parent_range
def clean(self):
cleaned_data = super(InetNumForm, self).clean()
@ -177,6 +187,9 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
if self.instance and self.instance.pk:
self.fields["parent_block"].disabled = True
mnts = self._user.maintainer_set.all()
self.fields['parent_block'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "parent_block")
def clean(self):
cleaned_data = super(ASBlockForm, self).clean()
@ -229,6 +242,9 @@ class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
else:
self.fields["asblock"].disabled = True
mnts = self._user.maintainer_set.all()
self.fields['asblock'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "asblock")
def clean(self):
cleaned_data = super(ASNumberForm, self).clean()

View File

@ -1,8 +1,9 @@
import whoisdb.models
import domains.models
def _addFields(fields, obj, fieldNames):
for fieldName in fieldNames:
fields.append((fieldName.capitalize(), getattr(obj, fieldName)))
fields.append((fieldName.capitalize().replace("_", " "), getattr(obj, fieldName)))
def getWhoisObjectFields(obj, owner):
@ -27,7 +28,15 @@ def getWhoisObjectFields(obj, owner):
elif c == whoisdb.models.InetNum:
_addFields(fields, obj, ["name"])
fields.append(("Address CIDR", obj.prefix()))
_addFields(fields, obj, ["description", "mnt_by", "mnt_lower", "admin_c"])
_addFields(fields, obj, ["description", "parent_range", "mnt_by", "mnt_lower", "admin_c"])
elif c == domains.models.Domain:
_addFields(fields, obj, ["name", "nameservers", "mnt_by", "admin_c"])
elif c == domains.models.Nameserver:
_addFields(fields, obj, ["name", "glueIPv4", "glueIPv6", "mnt_by", "admin_c"])
elif c == domains.models.ReverseZone:
_addFields(fields, obj, ["name"])
fields.append(("Address CIDR", obj.prefix()))
_addFields(fields, obj, ["parentNet", "nameservers"])
return fields

View File

@ -1,4 +1,5 @@
from django.db import models
from django.db.models import Q
from django.urls import reverse
from dncore.models import User
@ -111,6 +112,20 @@ class MntdObject(WhoisObject):
return True
return False
@classmethod
def getMntQueryset(clazz, mnts, instance, attr=None):
mntQ = Q(mnt_by__in=mnts)
if hasattr(clazz, "mnt_lower"):
mntQ |= Q(mnt_lower__in=mnts)
qs = clazz.objects.filter(mntQ)
if attr and instance and instance.pk:
if type(getattr(instance, attr)) == models.ManyToManyField:
qs |= getattr(instance, attr).all()
else:
qs |= clazz.objects.filter(pk=instance.pk)
return qs.distinct()
class Contact(MntdObject):
handleSuffix = "DN"

View File

@ -1,9 +1,11 @@
from django.core import validators
from django.core.exceptions import ValidationError
from django.utils import six
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _
import re
import ipaddress
@deconstructible
@ -26,3 +28,12 @@ class HandleValidatorWithSuffix(validators.RegexValidator):
)
super(HandleValidatorWithSuffix, self).__init__()
def IP46CIDRValidator(value):
if not re.match(r"[0-9:.]+/[0-9]+", value):
raise ValidationError("Address needs to be a subnet in the format of ip/cidr")
try:
ipaddress.ip_network(value)
except ValueError as e:
raise ValidationError(str(e))