Viewing   / Svedrin / python-django-pdns / pdns/models.py [ raw ] (Rendered using Pygments Lexer: Python)

# -*- coding: utf-8 -*-

import time

from django.conf		import settings
from django.db			import models
from django.db.models		import Q
from django.contrib.auth.models import User

from pdns.conf			import settings as pdns_settings


class Supermaster(models.Model):
	""" SuperMasters are master servers which are allowed to push domains to us,
	    even if they are not listed with us as a slave domain yet.
	
	    For more info about SuperSlave operation, see:
	    http://downloads.powerdns.com/documentation/html/generic-mypgsql-backends.html#AEN6061
	"""
	
	ip              = models.CharField( max_length=25 )
	nameserver      = models.CharField( 'Name des Nameservers', max_length=255 )
	account         = models.CharField( 'Accountname', max_length=40, unique=True )
	
	def __unicode__(self):
		return self.nameserver
	
	class Meta:
		db_table = 'supermasters'


class Domain(models.Model):
	""" Registers a domain with the server as a NATIVE, MASTER or SLAVE domain.
	
	    In SuperSlave operation, the "account" field will specify which SuperMaster
	    we received this domain from.
	"""
	
	TYPE_CHOICES    = (
		( "NATIVE", "Nativ: Daten aus DB holen, aber nicht zur Replikation freigeben" ),
		( "MASTER", "Master: Wie Nativ, andere Server dürfen replizieren" ),
		( "SLAVE",  "Slave: Replikation von einem Masterserver" ),
		)
	
	name		= models.CharField(     'Name der Domain', max_length=255, unique=True )
	master		= models.CharField(     'Masterserver bei Slaves', max_length=20, blank=True )
	last_check	= models.DateTimeField( 'Datum der letzten Prüfung auf Änderungen', null=True, blank=True, editable=False )
	dtype		= models.CharField(     'Typ', db_column="type", max_length=6, choices=TYPE_CHOICES,
						default=pdns_settings.DEFAULT_DOMAIN_TYPE )
	notified_serial = models.IntegerField(  'Letzte im SOA benutzte Serial', null=True, blank=True, editable=False )
	account 	= models.ForeignKey(    Supermaster, db_column="account", null=True, blank=True, to_field="account" )
	owner		= models.ForeignKey(    User, null=True, blank=True )
	
	def __unicode__(self):
		return self.name
	
	class Meta:
		db_table = 'domains'
	
	soa_record = property(
		lambda self: self.record_set.get( rrtype="SOA" ),
		doc="The SOA record for this domain."
		);
	
	def create_soa_record( self, refresh=None, retry=None, expire=None, default_ttl=None ):
		""" Create or change the SOA record for this domain.
		
		    Calling this method is NOT necessary to update the serial if you changed
		    a record, it is ONLY necessary if anything apart from the serial has changed
		    or no SOA record exists.
		"""
		if self.owner is not None:
			hostmaster = self.owner.email.replace( "@", "." )
		else:
			hostmaster = pdns_settings.HOSTMASTER_MAILADDR;
		
		if refresh     is None: refresh     = pdns_settings.DEFAULT_SOA_REFRESH
		if retry       is None: retry       = pdns_settings.DEFAULT_SOA_RETRY
		if expire      is None: expire      = pdns_settings.DEFAULT_SOA_EXPIRE
		if default_ttl is None: default_ttl = pdns_settings.DEFAULT_TTL
		
		try:
			soa = self.soa_record
		except Record.DoesNotExist:
			soa = self.record_set.create( name="", rrtype="SOA" )
		
		# The SOA record format is "primary-NS hostmaster serial refresh retry expire default_ttl"
		# set serial to 0 to have it auto-assigned by PowerDNS.
		soa.content = "%(primns)s %(hostmaster)s 0 %(refresh)d %(retry)d %(expire)d %(ttl)d" % {
			'primns':     pdns_settings.SERVER_FQDN,
			'hostmaster': hostmaster,
			'refresh':    refresh,
			'retry':      retry,
			'expire':     expire,
			'ttl':        default_ttl,
			}
		
		soa.save()
	
	
	def create_or_update_addr( self, name, address, rrtype="A" ):
		""" Make sure `name` is the only RR in this domain that resolves to `address`. """
		
		self.delete_addr( name, address, rrtype )
		
		rec = self.record_set.create( name=name, rrtype=rrtype, content=address )
		rec.save()
	
	def delete_addr( self, name, address, rrtype="A" ):
		""" Delete all records of the given type with the given name or address. """
		self.record_set.filter( ( Q(content=address) | Q(name=name) ) & Q(rrtype=rrtype) ).delete()
	
	
	def save( self, *args, **kwargs ):
		""" Save the Domain, automatically creating a SOA record for new domains. """
		makeSoa = ( self.id is None )
		models.Model.save( self, *args, **kwargs )
		if makeSoa:
			self.create_soa_record()


class Record(models.Model):
	""" A record for a domain.
	
	    For documentation on the fields, see:
	    http://downloads.powerdns.com/documentation/html/generic-mypgsql-backends.html#AEN5878
	"""
	
	TYPE_CHOICES = (
		( 'A',     'A:     IPv4-Adresse' ),
		( 'AAAA',  'AAAA:  IPv6-Adresse' ),
		( 'CNAME', 'CName: Weiterleitung auf anderen Namen' ),
		( 'PTR',   'PTR:   Hostname für Reverse-DNS-Eintrag' ),
		( 'TXT',   'TXT:   Beliebiger Text' ),
		( 'SOA',   'SOA:   Allgemeine Angaben zur Domain' ),
		( 'MX',    'MX:    Mailserver' ),
		( 'NS',    'NS:    Nameserver' ),
		( 'SRV',   'SRV:   Services' ),
		( 'SPF',   'SPF:   Sender Permitted From' ),
		)
	
	domain		= models.ForeignKey(	Domain )
	name		= models.CharField(	'Name',    max_length=255, blank=True )
	rrtype		= models.CharField(	'Klasse',  max_length=6, db_column="type", choices=TYPE_CHOICES,
						default=pdns_settings.DEFAULT_RECORD_TYPE )
	content 	= models.CharField(	'Eintrag', max_length=255 )
	ttl		= models.IntegerField(  null=True, blank=True, default=pdns_settings.DEFAULT_TTL )
	prio		= models.IntegerField(  null=True, blank=True, default=0 )
	change_date	= models.IntegerField(  editable=False )
	
	def __unicode__(self):
		return "%s (%s: %s)" % ( self.name, self.rrtype, self.content )
	
	class Meta:
		db_table = 'records'
	
	def get_relative_name( self ):
		""" Return the path from the domain name downward. """
		if self.name == self.domain.name:
			return ''
		else:
			domlen = len( self.domain.name )
			return self.name[:-(domlen+1)]
	
	relname = property( get_relative_name, doc=get_relative_name.__doc__ )
	
	def save( self, *args, **kwargs ):
		""" Save the record, making sure the name field contains an FQDN. This will also
		    update the change_date field to tell PowerDNS that it needs to NOTIFY the domain.
		"""
		
		if not self.name.endswith( self.domain.name ):
			if self.name:
				self.name = "%s.%s" % ( self.name, self.domain.name )
			else:
				self.name = self.domain.name
		
		self.change_date = time.time()
		
		models.Model.save( self, *args, **kwargs )