from django import forms from django.db.models import Case, When, IntegerField from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber import re import ipaddress class WhoisObjectFormMixin(object): def __init__(self, user, *args, **kwargs): super(WhoisObjectFormMixin, self).__init__(*args, **kwargs) self._user = user instance = getattr(self, 'instance', None) if instance and instance.pk: self.fields['handle'].widget.attrs['readonly'] = True # only show users contacts and already present contacts 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() def clean_handle(self): instance = getattr(self, 'instance', None) if instance and instance.pk: return instance.handle else: return self.cleaned_data['handle'] def clean(self): 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")) return cleaned_data class MntFormMixin(object): protectedFields = [] def __init__(self, lower=False, *args, **kwargs): super(MntFormMixin, self).__init__(*args, **kwargs) self._editLower = lower if self._editLower: for key in self.protectedFields: self.fields[key].disabled = True #self.fields["mnt_by"].widget.attrs["data-role"] = "tagsinput" instance = getattr(self, "instance", None) mntWhens = [When(auth=self._user, then=2)] if instance and instance.pk: 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 class MntForm(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: model = Maintainer fields = ['handle', 'description'] class ContactForm(WhoisObjectFormMixin, forms.ModelForm): class Meta: model = Contact 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: model = Contact fields = ['handle', 'name'] class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): prefix = forms.CharField() protectedFields = ['handle', 'protocol', 'parent_range', 'mnt_by', 'prefix'] class Meta: model = InetNum fields = ['handle', 'protocol', 'parent_range', 'prefix', 'name', 'description', 'mnt_by', 'mnt_lower'] 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 def clean_prefix(self): # make sure this is a subnet we're getting net = self.cleaned_data['prefix'] if not re.match(r"[0-9:.]+/[0-9]+", net): raise forms.ValidationError("Address needs to be a subnet in the format of ip/cidr") try: net = ipaddress.ip_network(net) except ValueError as e: raise forms.ValidationError(str(e)) return net def clean_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): cleaned_data = super(InetNumForm, self).clean() if not self._editLower: if not self.errors: prefix = cleaned_data['prefix'] parent = cleaned_data['parent_range'] if parent: parentNet = parent.getNetwork() if cleaned_data['protocol'] != parent.protocol: raise forms.ValidationError("Protocol type for prefix must be same as parent network") # check if in parent block if prefix.network_address not in parentNet or prefix.prefixlen < parentNet.prefixlen: raise forms.ValidationError("Prefix must be inside parent network range") # check if parent block has net that overlaps with us for otherNet in parent.inetnum_set.all(): if self.instance and self.instance.pk == otherNet.pk: continue if otherNet.getNetwork().overlaps(prefix): raise forms.ValidationError("The given prefix overlaps with network %s" % otherNet.handle) self.instance.address = str(prefix.network_address) self.instance.netmask = prefix.prefixlen return cleaned_data class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): protectedFields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'mnt_by'] # FIXME: Filter blocks class Meta: model = ASBlock fields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'name', 'description', 'mnt_by', 'mnt_lower'] def __init__(self, *args, **kwargs): super(ASBlockForm, self).__init__(*args, **kwargs) if not self.instance or self.instance and self.instance and self.instance.parent_block: self.fields["parent_block"].required = True if self.instance and self.instance.pk: self.fields["parent_block"].disabled = True def clean(self): cleaned_data = super(ASBlockForm, self).clean() if not self.errors: asBegin = cleaned_data['asBegin'] asEnd = cleaned_data['asEnd'] parent = cleaned_data['parent_block'] # check if somebody is already using this block # check if in range if asBegin > asEnd: raise forms.ValidationError("AS beginning must be smaller or equal to AS end") if parent: if parent.asnumber_set.count() > 0: raise forms.ValidationError("The parent AS block is already references by following AS number objects: %s" % (", ".join(map(lambda _x: _x.handle, parent.asnumber_set.all())),)) # check if same range if not (asBegin >= parent.asBegin and asEnd <= parent.asEnd): raise forms.ValidationError("AS beginning and end must be inside the range of the parent AS block") if parent.asBegin == asBegin and parent.asEnd == asEnd: raise forms.ValidationError("The range of this block cannot be the same range AS the parent AS block") # check for overlap with other asblocks for block in parent.asblock_set.all(): if self.instance and self.instance.pk and block.pk == self.instance.pk: continue if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \ asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd: raise forms.ValidationError("Block overlaps with block %s" % block.handle) return cleaned_data class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm): protectedFields = ['handle', 'asblock', 'number', 'mnt_by'] class Meta: model = ASNumber fields = ['handle', 'asblock', 'number', 'volatile', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c'] def __init__(self, *args, **kwargs): super(ASNumberForm, self).__init__(*args, **kwargs) if not (self.instance and self.instance.pk): self.fields["asblock"].required = True else: self.fields["asblock"].disabled = True def clean(self): cleaned_data = super(ASNumberForm, self).clean() if not self.errors: number = cleaned_data['number'] block = cleaned_data['asblock'] # belongs to asblock? if number < block.asBegin or number > block.asEnd: raise forms.ValidationError("AS number is not inside AS block") # does an entry already exist? try: otherAS = ASNumber.objects.get(number=number) if not (self.instance and self.instance.pk and self.instance.pk == otherAS.pk): raise forms.ValidationError("This AS number is already represented by %s" % otherAS.handle) except ASNumber.DoesNotExist: pass # has already other asblock? if block.asblock_set.count() > 0: raise forms.ValidationError("The given AS block is already references by following sub AS blocks: %s" % (", ".join(map(lambda _x: _x.handle, block.asblock_set.all())),))