from django import forms from django.core.exceptions import ValidationError from django.utils.text import capfirst from django.utils.translation import ugettext_lazy as _ from orchestra.admin.forms import AdminFormSet, AdminFormMixin from . import validators from .helpers import domain_for_validation from .models import Domain, Record class BatchDomainCreationAdminForm(forms.ModelForm): name = forms.CharField(label=_("Names"), widget=forms.Textarea(attrs={'rows': 5, 'cols': 50}), help_text=_("Fully qualified domain name per line. " "All domains will have the provided account and records.")) def clean_name(self): self.extra_names = [] target = None existing = set() errors = [] domain_names = self.cleaned_data['name'].strip().splitlines() for name in domain_names: name = name.strip() if not name: continue if name in existing: errors.append(ValidationError(_("%s domain name provided multiple times.") % name)) existing.add(name) if target is None: target = name else: domain = Domain(name=name) try: domain.full_clean(exclude=['top']) except ValidationError as e: for error in e.error_dict['name']: for msg in error.messages: errors.append( ValidationError("%s: %s" % (name, msg)) ) self.extra_names.append(name) if errors: raise ValidationError(errors) return target def clean(self): """ inherit related parent domain account, when exists """ cleaned_data = super().clean() if not cleaned_data['account']: account = None domain_names = [] if 'name' in cleaned_data: first = cleaned_data['name'] domain_names.append(first) domain_names.extend(self.extra_names) for name in domain_names: parent = Domain.objects.get_parent(name) if not parent: # Fake an account to make django validation happy account_model = self.fields['account']._queryset.model cleaned_data['account'] = account_model() raise ValidationError({ 'account': _("An account should be provided for top domain names."), }) elif account and parent.account != account: # Fake an account to make django validation happy account_model = self.fields['account']._queryset.model cleaned_data['account'] = account_model() raise ValidationError({ 'account': _("Provided domain names belong to different accounts."), }) account = parent.account cleaned_data['account'] = account return cleaned_data def full_clean(self): # set extra_names on instance to use it on inline formsets validation super().full_clean() self.instance.extra_names = extra_names class RecordForm(forms.ModelForm): class Meta: fields = ('ttl', 'type', 'value') def __init__(self, *args, **kwargs): super(RecordForm, self).__init__(*args, **kwargs) self.fields['ttl'].widget = forms.TextInput(attrs={'size': '10'}) self.fields['value'].widget = forms.TextInput(attrs={'size': '100'}) class ValidateZoneMixin(object): def clean(self): """ Checks if everything is consistent """ super(ValidateZoneMixin, self).clean() if any(self.errors): return is_host = True for form in self.forms: if form.cleaned_data.get('type') in (Record.TXT, Record.SRV, Record.CNAME): is_host = False break domain_names = [] if self.instance.name: domain_names.append(self.instance.name) domain_names.extend(getattr(self.instance, 'extra_names', [])) errors = [] for name in domain_names: records = [] for form in self.forms: data = form.cleaned_data if data and not data['DELETE']: records.append(data) if '_' in name and is_host: errors.append(ValidationError( _("%s: Hosts can not have underscore character '_', consider providing a SRV, CNAME or TXT record.") % name )) domain = domain_for_validation(self.instance, records) try: validators.validate_zone(domain.render_zone()) except ValidationError as error: for msg in error: errors.append( ValidationError("%s: %s" % (name, msg)) ) if errors: raise ValidationError(errors) class RecordEditFormSet(ValidateZoneMixin, AdminFormSet): pass class RecordInlineFormSet(ValidateZoneMixin, forms.models.BaseInlineFormSet): pass class SOAForm(AdminFormMixin, forms.Form): refresh = forms.CharField() clear_refresh = forms.BooleanField(label=_("Clear refresh"), required=False, help_text=_("Remove custom refresh value for all selected domains.")) retry = forms.CharField() clear_retry = forms.BooleanField(label=_("Clear retry"), required=False, help_text=_("Remove custom retry value for all selected domains.")) expire = forms.CharField() clear_expire = forms.BooleanField(label=_("Clear expire"), required=False, help_text=_("Remove custom expire value for all selected domains.")) min_ttl = forms.CharField() clear_min_ttl = forms.BooleanField(label=_("Clear min TTL"), required=False, help_text=_("Remove custom min TTL value for all selected domains.")) def __init__(self, *args, **kwargs): super(SOAForm, self).__init__(*args, **kwargs) for name in self.fields: if not name.startswith('clear_'): field = Domain._meta.get_field(name) self.fields[name] = forms.CharField( label=capfirst(field.verbose_name), help_text=field.help_text, validators=field.validators, required=False, )