django-powerdns-manager / src / powerdns_manager /

# -*- coding: utf-8 -*-
#  This file is part of django-powerdns-manager.
#  django-powerdns-manager is a web based PowerDNS administration panel.
#  Development Web Site:
#    -
#  Public Source Code Repository:
#    -
#  Copyright 2012 George Notaras <gnot [at]>
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  See the License for the specific language governing permissions and
#  limitations under the License.

from django.contrib import admin
from django.db.models.loading import cache
from django.contrib import messages
from django.contrib.admin import SimpleListFilter
from django.utils.translation import ugettext_lazy as _
from django.utils.crypto import get_random_string

from powerdns_manager import settings
from powerdns_manager.forms import SoaRecordModelForm
from powerdns_manager.forms import NsRecordModelForm
from powerdns_manager.forms import MxRecordModelForm
from powerdns_manager.forms import SrvRecordModelForm
from powerdns_manager.forms import ARecordModelForm
from powerdns_manager.forms import AaaaRecordModelForm
from powerdns_manager.forms import CnameRecordModelForm
from powerdns_manager.forms import PtrRecordModelForm
from powerdns_manager.forms import TxtRecordModelForm
from powerdns_manager.forms import DsRecordModelForm
from powerdns_manager.forms import CertRecordModelForm
from powerdns_manager.forms import HinfoRecordModelForm
from powerdns_manager.forms import LocRecordModelForm
from powerdns_manager.forms import SpfRecordModelForm
from powerdns_manager.forms import SshfpRecordModelForm
from powerdns_manager.forms import RpRecordModelForm
from powerdns_manager.forms import NaptrRecordModelForm
from powerdns_manager.forms import AfsdbRecordModelForm
from powerdns_manager.forms import DnskeyRecordModelForm
from powerdns_manager.forms import KeyRecordModelForm
from powerdns_manager.forms import NsecRecordModelForm
from powerdns_manager.forms import RrsigRecordModelForm
from powerdns_manager.signal_cb import zone_saved
from powerdns_manager.actions import set_domain_type_bulk
from powerdns_manager.actions import set_ttl_bulk

class BaseTabularRecordInline(admin.TabularInline):
    RR_TYPE = '__OVERRIDE__'
    form = '__OVERRIDE__'
    model = cache.get_model('powerdns_manager', 'Record')
    extra = 0
    fields = ('name', 'ttl', 'content', 'date_modified')
    readonly_fields = ('date_modified', )
    def __init__(self, *args, **kwargs):
        self.verbose_name = '%s Resource Record' % self.RR_TYPE
        self.verbose_name_plural = '%s Resource Records' % self.RR_TYPE
        super(BaseTabularRecordInline, self).__init__(*args, **kwargs)
    def queryset(self, request):
        """Return only RR_TYPE records"""
        qs = super(BaseTabularRecordInline, self).queryset(request)
        return qs.filter(type=self.RR_TYPE)

class SoaRecordInline(admin.StackedInline):
    model = cache.get_model('powerdns_manager', 'Record')
    form = SoaRecordModelForm
    # Show exactly one form
    extra = 1
    max_num = 1
    verbose_name = 'SOA Resource Record'
    verbose_name_plural = 'SOA Resource Record' # Only one SOA RR per zone
    # The ``name`` field is not available for editing. It is always set to the
    # name of the domain in ```` method.
    fields = ('ttl', 'primary', 'hostmaster', 'serial', 'refresh', 'retry', 'expire', 'default_ttl', 'date_modified')
    readonly_fields = ('date_modified', )
    can_delete = False
    def queryset(self, request):
        """Return only SOA records"""
        qs = super(SoaRecordInline, self).queryset(request)
        return qs.filter(type='SOA')

class NsRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'NS'
    form = NsRecordModelForm

class MxRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'MX'
    form = MxRecordModelForm
    fields = ('name', 'ttl', 'prio', 'content', 'date_modified')
class SrvRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'SRV'
    form = SrvRecordModelForm
    fields = ('name', 'ttl', 'prio', 'weight', 'port', 'target', 'date_modified')

class ARecordInline(BaseTabularRecordInline):
    RR_TYPE = 'A'
    form = ARecordModelForm

class AaaaRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'AAAA'
    form = AaaaRecordModelForm

class CnameRecordInline(BaseTabularRecordInline):
    form = CnameRecordModelForm

class PtrRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'PTR'
    form = PtrRecordModelForm

class TxtRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'TXT'
    form = TxtRecordModelForm

class DsRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'DS'
    form = DsRecordModelForm

class CertRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'CERT'
    form = CertRecordModelForm

class HinfoRecordInline(BaseTabularRecordInline):
    form = HinfoRecordModelForm

class LocRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'LOC'
    form = LocRecordModelForm

class SpfRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'SPF'
    form = SpfRecordModelForm

class SshfpRecordInline(BaseTabularRecordInline):
    form = SshfpRecordModelForm

class RpRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'RP'
    form = RpRecordModelForm

class NaptrRecordInline(BaseTabularRecordInline):
    form = NaptrRecordModelForm

class AfsdbRecordInline(BaseTabularRecordInline):
    form = AfsdbRecordModelForm

class DnskeyRecordInline(BaseTabularRecordInline):
    form = DnskeyRecordModelForm

class KeyRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'KEY'
    form = KeyRecordModelForm

class NsecRecordInline(BaseTabularRecordInline):
    RR_TYPE = 'NSEC'
    form = NsecRecordModelForm

class RrsigRecordInline(BaseTabularRecordInline):
    form = RrsigRecordModelForm

class EmptyNonTerminalRecordInline(admin.TabularInline):
    """Special inline for empty non-terminals supported by PowerDNS 3.2.
    model = cache.get_model('powerdns_manager', 'Record')
    extra = 0
    verbose_name = 'Empty Non-Terminal Resource Record'
    verbose_name_plural = 'Empty Non-Terminal Resource Record' # Only one SOA RR per zone
    fields = ('name', 'ttl', 'content', 'date_modified')
    readonly_fields = ('name', 'ttl', 'content', 'date_modified')
    can_delete = False
    def queryset(self, request):
        """Return only Empty Non-Terminal records"""
        qs = super(EmptyNonTerminalRecordInline, self).queryset(request)
        return qs.filter(type__isnull=True)

class DomainMetadataInline(admin.TabularInline):
    model = cache.get_model('powerdns_manager', 'DomainMetadata')
    fields = ('kind', 'content', 'date_modified')
    readonly_fields = ('date_modified', )
    extra = 0
    verbose_name_plural = 'Domain Metadata'

class CryptoKeyInline(admin.TabularInline):
    model = cache.get_model('powerdns_manager', 'CryptoKey')
    fields = ('flags', 'active', 'content', 'date_modified')
    readonly_fields = ('date_modified', )
    extra = 0
    verbose_name_plural = 'Crypto Keys'

class DomainAdmin(admin.ModelAdmin):
    #form = DomainModelForm
    #actions = [test_action, ]
    fields = ('name', 'type', 'master', 'date_modified')
    readonly_fields = ('date_modified', )
    list_display = ('name', 'type', 'master', 'date_modified')
    list_filter = ('type', )
    search_fields = ('name', 'master')
    verbose_name = 'zone'
    verbose_name_plural = 'zones'
    save_on_top = True
    actions = [set_domain_type_bulk, set_ttl_bulk]
    change_list_template = 'powerdns_manager/domain_changelist.html'
    # Build the ``inlines`` list. Only inlines for enabled RR types are included.
    inlines = []
    # Resource Record type to Resource Record Inline Map
        'A':        ARecordInline,
        'AAAA':     AaaaRecordInline,
        'AFSDB':    AfsdbRecordInline,
        'CERT':     CertRecordInline,
        'CNAME':    CnameRecordInline,
        'DNSKEY':   DnskeyRecordInline,
        'DS':       DsRecordInline,
        'HINFO':    HinfoRecordInline,
        'KEY':      KeyRecordInline,
        'LOC':      LocRecordInline,
        'MX':       MxRecordInline,
        'NAPTR':    NaptrRecordInline,
        'NS':       NsRecordInline,
        'NSEC':     NsecRecordInline,
        'PTR':      PtrRecordInline,
        'RP':       RpRecordInline,
        'RRSIG':    RrsigRecordInline,
        'SOA':      SoaRecordInline,
        'SPF':      SpfRecordInline,
        'SSHFP':    SshfpRecordInline,
        'SRV':      SrvRecordInline,
        'TXT':      TxtRecordInline,
    # Add RR inlines
    for RR_TYPE in settings.PDNS_ENABLED_RR_TYPES:
    # Add other inlines
    #inlines.append(EmptyNonTerminalRecordInline)    # TODO: empty non-terminal record support is for the future
    def queryset(self, request):
        qs = super(DomainAdmin, self).queryset(request)
        if not request.user.is_superuser:
            # Non-superusers see the domains they have created
            qs = qs.filter(created_by=request.user)
        return qs
    def save_model(self, request, obj, form, change):
        if not change:
            obj.created_by = request.user
        # TODO: Find a way to update the zone serial (SOA record)
        # At this point, the records have not been saved yet.
        # Perhaps with a signal in save_related()
    def save_related(self, request, form, formsets, change):
        """Calls the signal that rectifies the zone.
        In ModelAdmin.add_view() and ModelAdmin.change_view() the method
        save_model() is normally called before save_related().
        Using a post_save signal on the Domain or Record models is not
        efficient. In case of the Domain model, rectify_zone() would not process
        any new data in the associated records. In case of the Record model,
        rectify_zone() would be called multiple times and only the last call
        would be the effective one.
        rectify_zone() must be called after all the records and the domain have
        been saved to the database.
        Here we execute the parent save_related() and then we call rectify zone
        through a custom signal.
        super(DomainAdmin, self).save_related(request, form, formsets, change)
        # Send the zone_saved signal
        zone_saved.send(sender=self.model, origin=form.cleaned_data.get('name'))'powerdns_manager', 'Domain'), DomainAdmin)

class TsigKeyAdmin(admin.ModelAdmin):
    fields = ('name', 'algorithm', 'secret', 'date_modified')
    readonly_fields = ('date_modified', )
    list_display = ('name', 'algorithm', 'date_modified')
    list_filter = ('algorithm', )
    search_fields = ('name', )
    verbose_name = 'TSIG Key'
    verbose_name_plural = 'TSIG Keys'
    def queryset(self, request):
        qs = super(TsigKeyAdmin, self).queryset(request)
        if not request.user.is_superuser:
            # Non-superusers see the records they have created
            qs = qs.filter(created_by=request.user)
        return qs
    def save_model(self, request, obj, form, change):
        if not change:
            obj.created_by = request.user'powerdns_manager', 'TsigKey'), TsigKeyAdmin)

class SuperMasterAdmin(admin.ModelAdmin):
    fields = ('ip', 'nameserver', 'account', 'date_modified')
    readonly_fields = ('date_modified', )
    list_display = ('ip', 'nameserver', 'account', 'date_modified')
    search_fields = ('nameserver', 'account')
    verbose_name = 'SuperMaster'
    verbose_name_plural = 'SuperMasters''powerdns_manager', 'SuperMaster'), SuperMasterAdmin)

class DynamicZoneAdmin(admin.ModelAdmin):
    fields = ('domain', 'api_key', 'date_created', 'date_modified')
    readonly_fields = ('api_key', 'date_created', 'date_modified')
    list_display = ('domain', 'date_created', 'date_modified')
    search_fields = ('domain', )
    verbose_name = 'Dynamic Zone'
    verbose_name_plural = 'Dynamic Zones'
    actions = ['reset_api_key']

    def reset_api_key(self, request, queryset):
        for obj in queryset:
            obj.api_key = self._get_new_api_key()
    reset_api_key.short_description = "Reset API Key"
    def queryset(self, request):
        qs = super(DynamicZoneAdmin, self).queryset(request)
        if not request.user.is_superuser:
            # Non-superusers see the dynamic zones they have created
            qs = qs.filter(created_by=request.user)
        return qs
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        Domain = cache.get_model('powerdns_manager', 'Domain')
        if == 'domain':
            if not request.user.is_superuser:    # Superusers see the full choice list
                kwargs["queryset"] = Domain.objects.filter(
                    created_by=request.user, powerdns_manager_dynamiczone_domain__isnull=True)
        return super(DynamicZoneAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
    def save_model(self, request, obj, form, change):
        if not change:
            obj.api_key = self._get_new_api_key()
            obj.created_by = request.user
    def _get_new_api_key(self):
        return get_random_string(
            length=24, allowed_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')'powerdns_manager', 'DynamicZone'), DynamicZoneAdmin)