Merge branch 'bump-bonanza-and-python3'

This commit is contained in:
nikolark committed 2015-10-07 10:42:55 +02:00
commit 8946234b10
29 files changed
+3585 -479

No files matched your search

+2 -2
View File
@@ -2,7 +2,7 @@
*.swp
temp
*.log
settings.py
dns_mdb.db
local_settings.py
db.sqlite3
/.idea
/venv
-17
View File
@@ -1,17 +0,0 @@
A Django app for managing computer equipment.
Generates DHCP configs and zone files.
Dependencies:
sudo apt-get install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils
Setup environment:
mdb$ virtualenv venv
mdb$ . venv/bin/activate
(venv)mdb$ pip install -r requirements.txt
Create a config file:
mdb$ cp mdb/settings-sample.py mdb/settings.py
# Edit settings.py with your favorite editor
Run the server:
(venv)mdb$ python manage.py runserver
+21
View File
@@ -0,0 +1,21 @@
A Django app for managing computer equipment.
## Installation
sudo apt install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils
virtualenv venv
. venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver
## Development tasks
Dump test data:
python manage.py dumpdata mdb --natural-foreign --indent=4 > mdb/fixtures/test_data.json
## Features
* Creates DHCPD configuration files
* Creates DNS zone files for BIND
* Creates PXE config files (with random secret for use with Puppet autosign)
* Partial IPv6 support
+3 -4
View File
@@ -1,9 +1,8 @@
#!/usr/bin/env python
import os, sys
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mdb.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mdbsite.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
+1
View File
@@ -0,0 +1 @@
default_app_config = 'mdb.apps.MdbAppConfig'
+29 -6
View File
@@ -1,5 +1,8 @@
from mdb.models import *
from django.contrib import admin
from django.contrib import admin, messages
from mdb.models import Ip6Address, Interface, Ip4Address, DhcpOption, DhcpCustomField, DomainSrvRecord, DomainTxtRecord, \
DomainCnameRecord, DomainARecord, Domain, Host, Ip4Subnet, Ip6Subnet, Nameserver, MailExchange, OperatingSystem, \
HostType, DhcpConfig
class Ip6AddressInline(admin.TabularInline):
@@ -20,17 +23,17 @@ class HostAdmin(admin.ModelAdmin):
ordering = ('hostname',)
inlines = [InterfaceInline]
list_display = ['hostname', 'owner', 'host_type', 'location', 'mac_addresses', 'ip_addresses', 'in_domain',
'ipv6_enabled']
list_filter = ['host_type', 'owner', 'location']
'pxe_installable', 'ipv6_enabled']
list_filter = ['host_type', 'owner', 'location', 'pxe_installable']
readonly_fields = ['kerberos_principal_name', 'kerberos_principal_created_date',
'kerberos_principal_created']
search_fields = ['hostname', 'location', 'interface__macaddr', 'interface__ip4address__address']
fieldsets = (
('Owner Information', {
'fields': ('owner', 'location', 'description' )
'fields': ('owner', 'location', 'description')
}),
('Hardware and Software Information', {
'fields': (('brand', 'model', 'serial_number'), ('hostname', 'host_type'), ('operating_system', 'virtual'))
'fields': (('hostname', 'host_type'), ('pxe_key', 'pxe_installable'), ('brand', 'model'), 'serial_number', ('operating_system', 'virtual'))
}),
# FIXME: Not in use
# ('Domain and Kerberos Information', {
@@ -40,6 +43,26 @@ class HostAdmin(admin.ModelAdmin):
# ('kerberos_principal_name', 'kerberos_principal_created_date'))
# }),
)
actions = ['set_installable']
def _get_host_warning_message(self, host, ifs):
ifs_str = ', '.join([_if.name for _if in ifs])
return 'Host {} might not be installable since interface(s) {} has no IP set.'.format(host, ifs_str)
def set_installable(self, request, queryset):
invalid_ifs_per_host = []
for obj in queryset:
invalid_ifs = obj.interface_set.filter(ip4address__isnull=True)
if invalid_ifs.exists():
invalid_ifs_per_host.append(self._get_host_warning_message(obj, invalid_ifs))
obj.pxe_installable = True
obj.save() # triggers post save signal
if invalid_ifs_per_host:
self.message_user(request, message='\n'.join(invalid_ifs_per_host), level=messages.WARNING)
set_installable.short_description = "Mark selected hosts as PXE installable"
def get_queryset(self, request):
return super(HostAdmin, self).get_queryset(request).select_related()
View File
File renamed without changes.
+46
View File
@@ -0,0 +1,46 @@
from mdb.models import Host
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class HostPXEValidateSerializer(serializers.Serializer):
certname = serializers.CharField(required=True)
pxe_key = serializers.CharField(required=True)
host = None
def _get_host_by_fqdn(self, attrs):
fqdn = attrs['certname']
host_name = fqdn.split('.')[0]
domain = '.'.join(fqdn.split('.')[1:])
try:
return Host.objects.get(hostname=host_name, interface__domain__domain_name=domain)
except (Host.DoesNotExist, Host.MultipleObjectsReturned):
return None
def validate(self, attrs):
# custom validation
host = self._get_host_by_fqdn(attrs)
if host is None:
raise ValidationError('Host \'{}\' not found.'.format(attrs['certname']))
if not host.pxe_installable:
raise ValidationError('Host \'{}\' is marked as not installable via PXE.'.format(attrs['certname']))
if not host.pxe_key:
raise ValidationError('Host \'{}\' has no pxe_key.'.format(attrs['certname']))
if host.pxe_key != attrs['pxe_key']:
raise ValidationError('Supplied pxe_key does not match host \'{}\'.'.format(attrs['certname']))
self.host = host
return attrs
def create(self, validated_data):
# Make requests kind of idempotent by only allowing 1 request / installation
self.host.pxe_installable = False
self.host.save()
return self.host
+20
View File
@@ -0,0 +1,20 @@
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from mdb.api.serializers import HostPXEValidateSerializer
class HostPXEValidate(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = HostPXEValidateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
host = serializer.save() # sets Host.pxe_installable=False
return Response({'valid_pxe_key': True})
+10
View File
@@ -0,0 +1,10 @@
from django.apps import AppConfig
class MdbAppConfig(AppConfig):
name = 'mdb'
verbose_name = 'Machine Database'
def ready(self):
# all models are loaded, now attach signals
import mdb.signals
File diff suppressed because it is too large. Load diff
+9 -5
View File
@@ -6,7 +6,11 @@ from difflib import context_diff
from django.core.management.base import BaseCommand
from django.core.mail import mail_admins
from optparse import make_option
from commands import getstatusoutput
try:
from subprocess import getstatusoutput
except ImportError:
from commands import getstatusoutput
from mdb.models import DhcpConfig
@@ -42,13 +46,13 @@ class Command(BaseCommand):
# do we need to do anything?
if config.serial == config.active_serial and not force:
print "Nothing to do, serial is the same: %s" % config.serial
print("Nothing to do, serial is the same: %s" % config.serial)
sys.exit(0)
# write to stdout
if output == sys.stdout:
print config.dhcpd_configuration()
print 'Not writing to file, serial not updated'
print(config.dhcpd_configuration())
print('Not writing to file, serial not updated')
return
# read current file contents
@@ -71,7 +75,7 @@ class Command(BaseCommand):
tofile=config.serial,
lineterm=''))
print diff
print(diff)
# update serial
old_serial = config.active_serial
+9 -5
View File
@@ -7,8 +7,12 @@ from difflib import context_diff
from django.core.management.base import BaseCommand
from django.core.mail import mail_admins
from optparse import make_option
from commands import getstatusoutput
from mdb.models import *
try:
from subprocess import getstatusoutput
except ImportError:
from commands import getstatusoutput
from mdb.models import Domain, Ip4Subnet, Ip6Subnet
class Command(BaseCommand):
@@ -43,7 +47,7 @@ class Command(BaseCommand):
# do the binaries exist?
for f in (self.checkzone_bin, self.bind_bin):
if not os.path.isfile(f):
print "ERROR: no such file %s, exiting..." % f
print("ERROR: no such file %s, exiting..." % f)
if not debug:
sys.exit(1)
@@ -106,11 +110,11 @@ class Command(BaseCommand):
'diff': None,
}
print "updating %s %s [%d -> %d]" % (
print("updating %s %s [%d -> %d]" % (
zonetype,
zone.domain_name,
zone.domain_active_serial,
zone.domain_serial)
zone.domain_serial))
# generate zone file contents
new = zone.zone_file_contents()
+85
View File
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.core.validators
import re
class Migration(migrations.Migration):
dependencies = [
('mdb', '0005_auto_20150822_2204'),
]
operations = [
migrations.AlterModelOptions(
name='dhcpcustomfield',
options={'verbose_name': 'DHCP custom field'},
),
migrations.AlterField(
model_name='domain',
name='domain_admin',
field=models.EmailField(max_length=254),
),
migrations.AlterField(
model_name='domain',
name='domain_ipaddr',
field=models.GenericIPAddressField(protocol='IPv4'),
),
migrations.AlterField(
model_name='host',
name='hostname',
field=models.CharField(validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid hostname', regex=re.compile('^(?!-)[-a-z0-9]+(?<!-)$', 34))], max_length=64),
),
migrations.AlterField(
model_name='interface',
name='ip4address',
field=models.OneToOneField(to='mdb.Ip4Address', null=True, blank=True),
),
migrations.AlterField(
model_name='interface',
name='macaddr',
field=models.CharField(validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid MAC address', regex=re.compile('^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$', 34))], max_length=17),
),
migrations.AlterField(
model_name='ip4address',
name='address',
field=models.GenericIPAddressField(protocol='IPv4'),
),
migrations.AlterField(
model_name='ip4subnet',
name='dhcp_dynamic_end',
field=models.GenericIPAddressField(protocol='IPv4', null=True, blank=True),
),
migrations.AlterField(
model_name='ip4subnet',
name='dhcp_dynamic_start',
field=models.GenericIPAddressField(protocol='IPv4', null=True, blank=True),
),
migrations.AlterField(
model_name='ip4subnet',
name='domain_admin',
field=models.EmailField(max_length=254),
),
migrations.AlterField(
model_name='ip4subnet',
name='netmask',
field=models.GenericIPAddressField(protocol='IPv4'),
),
migrations.AlterField(
model_name='ip4subnet',
name='network',
field=models.GenericIPAddressField(protocol='IPv4'),
),
migrations.AlterField(
model_name='ip6subnet',
name='domain_admin',
field=models.EmailField(max_length=254),
),
migrations.AlterField(
model_name='operatingsystem',
name='arch',
field=models.CharField(max_length=255, choices=[('mips', 'MIPS'), ('arm', 'ARM'), ('broadcom', 'Broadcom'), ('rc32300', 'RC32300'), ('powerpc', 'PowerPC'), ('powerpc403ga', 'PowerPC403GA'), ('unknown', 'Unknown'), ('x86_64', 'x86-64'), ('i386', 'x86')], null=True, blank=True),
),
]
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('mdb', '0006_auto_20151001_2235'),
]
operations = [
migrations.AddField(
model_name='host',
name='pxe_installable',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='host',
name='pxe_key',
field=models.CharField(max_length=254, blank=True),
),
]
+19
View File
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('mdb', '0007_auto_20151003_1313'),
]
operations = [
migrations.AlterField(
model_name='ip6address',
name='address',
field=models.GenericIPAddressField(protocol='IPv6'),
),
]
+118 -216
View File
@@ -1,32 +1,33 @@
from django.db import models
from django.db.models import Count
from django.db.models.signals import post_save, pre_save, pre_delete
from django.dispatch import receiver
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from mdb.utils import host_as_pxe_files
from mdb.validators import validate_hostname, validate_macaddr
from validators import validate_hostname, validate_macaddr
import ipaddr
import ipaddress
import datetime
import re
@python_2_unicode_compatible
class Nameserver(models.Model):
hostname = models.CharField(max_length=256)
def __unicode__(self):
def __str__(self):
return self.hostname
@python_2_unicode_compatible
class MailExchange(models.Model):
priority = models.IntegerField()
hostname = models.CharField(max_length=256)
def __unicode__(self):
def __str__(self):
return "(" + str(self.priority) + ") " + self.hostname
@python_2_unicode_compatible
class Domain(models.Model):
domain_name = models.CharField(max_length=256)
domain_soa = models.CharField(max_length=256)
@@ -38,31 +39,32 @@ class Domain(models.Model):
domain_expire = models.IntegerField(default=604800)
domain_minimum_ttl = models.IntegerField(default=86400)
domain_admin = models.EmailField()
domain_ipaddr = models.IPAddressField()
domain_ipaddr = models.GenericIPAddressField(protocol='IPv4')
domain_filename = models.CharField(max_length=256)
created_date = models.DateTimeField(auto_now_add=True)
domain_nameservers = models.ManyToManyField(Nameserver)
domain_mailexchanges = models.ManyToManyField(MailExchange)
def __unicode__(self):
def __str__(self):
return self.domain_name
def num_records(self):
host_a_records = Host.objects.filter(interface__domain=self).count()
size = {
"cname": self.domaincnamerecord_set.count(),
"srv": self.domainsrvrecord_set.count(),
"txt": self.domaintxtrecord_set.count(),
"a": self.domainarecord_set.count() + host_a_records
"CNAME": self.domaincnamerecord_set.count(),
"SRV": self.domainsrvrecord_set.count(),
"TXT": self.domaintxtrecord_set.count(),
"A": self.domainarecord_set.count() + host_a_records
}
return size
return ', '.join(['{}: {}'.format(k, v) for k, v in size.items()])
num_records.short_description = "Num Records"
def __eq__(self, other):
if not other or not hasattr(other, 'domain_name'):
return False
return self.domain_name == other.domain_name
def zone_file_contents(self):
@@ -93,12 +95,12 @@ class Domain(models.Model):
content += "; SRV records\n"
for srv in self.domainsrvrecord_set.all():
content += unicode(srv) + "\n"
content += srv.as_record() + "\n"
content += "; A records\n"
for a in self.domainarecord_set.all():
content += unicode(a) + "\n"
content += a.as_record() + "\n"
content += "; CNAME records \n"
@@ -108,7 +110,7 @@ class Domain(models.Model):
content += "; TXT records \n"
for txt in self.domaintxtrecord_set.all():
content += unicode(txt) + "\n"
content += txt.as_record() + "\n"
content += "; HOST records\n"
@@ -126,6 +128,7 @@ class Domain(models.Model):
return content
@python_2_unicode_compatible
class DomainSrvRecord(models.Model):
srvce = models.CharField(max_length=64)
prot = models.CharField(max_length=64)
@@ -137,42 +140,65 @@ class DomainSrvRecord(models.Model):
domain = models.ForeignKey(Domain)
created_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.srvce + "." + self.prot + "." + self.name + ". IN SRV " \
+ str(self.priority) + " " + str(self.weight) + " " + \
str(self.port) + " " + self.target + "."
def as_record(self):
return '{}.{}.{}. IN SRV {} {} {} {}.'.format(
self.srvce,
self.prot,
self.name,
self.priority,
self.weight,
self.port,
self.target
)
def __str__(self):
return self.as_record()
@python_2_unicode_compatible
class DomainTxtRecord(models.Model):
name = models.CharField(max_length=256)
target = models.CharField(max_length=256)
domain = models.ForeignKey(Domain)
created_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.name + " TXT " + self.target
def as_record(self):
return '{} TXT {}'.format(self.name, self.target)
def __str__(self):
return self.as_record()
@python_2_unicode_compatible
class DomainCnameRecord(models.Model):
name = models.CharField(max_length=256)
target = models.CharField(max_length=256)
domain = models.ForeignKey(Domain)
created_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.name + " IN CNAME " + self.target
def as_record(self):
return '{} IN CNAME {}'.format(self.name, self.target)
def __str__(self):
return self.as_record()
@python_2_unicode_compatible
class DomainARecord(models.Model):
name = models.CharField(max_length=256)
target = models.CharField(max_length=256)
domain = models.ForeignKey(Domain)
created_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.name + " IN A " + self.target
def as_record(self):
return '{} IN A {}'.format(self.name, self.target)
def __str__(self):
return self.as_record()
@python_2_unicode_compatible
class DhcpConfig(models.Model):
serial = models.IntegerField()
active_serial = models.IntegerField()
@@ -214,28 +240,27 @@ class DhcpConfig(models.Model):
# time to write host definitions
for subnet in self.ip4subnet_set.all():
for ip4address in subnet.ip4address_set.all():
if ip4address.interface_set.count() == 0:
if not hasattr(ip4address, 'interface') or not ip4address.interface.dhcp_client:
continue
interface = ip4address.interface_set.get()
if not interface.dhcp_client:
continue
content += "\nhost %s {\n" % interface.host.hostname
content += "\thardware ethernet %s;\n" % interface.macaddr
_if = ip4address.interface
content += "\nhost %s {\n" % _if.host.hostname
content += "\thardware ethernet %s;\n" % _if.macaddr
content += "\tfixed-address %s.%s;\n" % \
(interface.host.hostname, interface.domain.domain_name)
if len(interface.pxe_filename) > 0:
content += "\tfilename \"%s\";\n" % interface.pxe_filename
(_if.host.hostname, _if.domain.domain_name)
if len(_if.pxe_filename) > 0:
content += "\tfilename \"%s\";\n" % _if.pxe_filename
content += "}\n"
return content
def __unicode__(self):
def __str__(self):
return self.name
class Meta:
verbose_name = 'DHCP config'
@python_2_unicode_compatible
class Ip6Subnet(models.Model):
name = models.CharField(max_length=255)
network = models.CharField(max_length=255)
@@ -254,7 +279,7 @@ class Ip6Subnet(models.Model):
domain_admin = models.EmailField()
domain_filename = models.CharField(max_length=256)
def __unicode__(self):
def __str__(self):
return self.network + " (" + self.name + ")"
def zone_file_contents(self, generate_unassigned=False):
@@ -292,7 +317,7 @@ class Ip6Subnet(models.Model):
addr.interface.host.hostname,
addr.interface.domain.domain_name)
ip = ipaddr.IPv6Address(self.network + addr.address)
ip = ipaddress.IPv6Address(self.network + addr.address)
ip = ".".join(ip.exploded.replace(":", "")[16:])[::-1]
content += "%s\tPTR\t%s.\n" % (ip, hostname)
@@ -305,10 +330,11 @@ class Ip6Subnet(models.Model):
verbose_name = 'IPv6 subnet'
@python_2_unicode_compatible
class Ip4Subnet(models.Model):
name = models.CharField(max_length=256)
netmask = models.IPAddressField()
network = models.IPAddressField()
netmask = models.GenericIPAddressField(protocol='IPv4')
network = models.GenericIPAddressField(protocol='IPv4')
created_date = models.DateTimeField(auto_now_add=True)
domain_name = models.CharField(max_length=255, editable=False)
domain_nameservers = models.ManyToManyField(Nameserver)
@@ -324,31 +350,28 @@ class Ip4Subnet(models.Model):
domain_filename = models.CharField(max_length=256)
dhcp_dynamic = models.BooleanField(default=False)
dhcp_dynamic_start = models.IPAddressField(null=True, blank=True)
dhcp_dynamic_end = models.IPAddressField(null=True, blank=True)
dhcp_dynamic_start = models.GenericIPAddressField(protocol='IPv4', null=True, blank=True)
dhcp_dynamic_end = models.GenericIPAddressField(protocol='IPv4', null=True, blank=True)
dhcp_config = models.ForeignKey(DhcpConfig)
def __unicode__(self):
def __str__(self):
return self.network + " (" + self.name + ")"
def num_addresses(self):
subnet = ipaddr.IPv4Network(self.network + "/" + self.netmask)
return subnet.numhosts
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return subnet.num_addresses
def broadcast_address(self):
subnet = ipaddr.IPv4Network(self.network + "/" + self.netmask)
return subnet.broadcast
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return subnet.broadcast_address
def first_address(self):
subnet = ipaddr.IPv4Network(self.network + "/" + self.netmask)
return subnet.iterhosts().next()
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return next(subnet.hosts())
def last_address(self):
subnet = ipaddr.IPv4Network(self.network + "/" + self.netmask)
for curr in subnet.iterhosts():
pass # horribly ineficcient
return curr
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return list(subnet.hosts())[-1]
broadcast_address.short_description = 'broadcast'
num_addresses.short_description = '#addresses'
@@ -378,26 +401,18 @@ class Ip4Subnet(models.Model):
content += ";\n"
for addr in self.ip4address_set.all():
if addr.interface_set.count() == 0 and generate_unassigned:
""" Get PTR records for each v4 address in subnet """
if hasattr(addr, 'interface') and addr.interface.domain:
hostname = "%s.%s" % (
addr.interface.host.hostname,
addr.interface.domain.domain_name)
content += "%-20s\tIN\tPTR\t%s.\n" % \
(addr.address.split(".")[3], hostname)
elif not hasattr(addr, 'interface') and generate_unassigned:
content += "%s\tIN\tPTR\t%s.%s\n" % (
addr.address,
addr.address.split(".")[3],
"dhcp.neuf.no.")
continue
for interface in addr.interface_set.all():
if interface.domain is None and generate_unassigned:
content += "%s\tIN\tPTR\t%s.%s.\n" % (
addr.address,
addr.address.split(".")[3],
"dhcp.neuf.no")
else:
hostname = "%s.%s" % (
interface.host.hostname,
interface.domain.domain_name)
content += "%-20s\tIN\tPTR\t%s.\n" % \
(addr.address.split(".")[3], hostname)
return content
@@ -405,12 +420,13 @@ class Ip4Subnet(models.Model):
verbose_name = 'IPv4 subnet'
@python_2_unicode_compatible
class DhcpOption(models.Model):
key = models.CharField(max_length=255)
value = models.CharField(max_length=255)
ip4subnet = models.ForeignKey(Ip4Subnet)
def __unicode__(self):
def __str__(self):
return self.key + " " + self.value
class Meta:
@@ -425,25 +441,22 @@ class DhcpCustomField(models.Model):
verbose_name = 'DHCP custom field'
@python_2_unicode_compatible
class Ip4Address(models.Model):
subnet = models.ForeignKey(Ip4Subnet)
address = models.IPAddressField()
address = models.GenericIPAddressField(protocol='IPv4')
last_contact = models.DateTimeField(null=True, blank=True)
ping_avg_rtt = models.FloatField(null=True, blank=True)
def __unicode__(self):
# FIXME: generates 2 SQL queries
_if = self.interface_set.all()
if not _if:
def __str__(self):
# FIXME: generates 2 SQL queries?
if not hasattr(self, 'interface'):
return self.address
return "{} ({})".format(
self.address,
_if.get().host.hostname
)
return "{} ({})".format(self.address, self.interface.host.hostname)
def assigned_to_host(self):
return self.interface_set.get().host
return self.interface.host
assigned_to_host.short_description = "Assigned to Host"
@@ -452,11 +465,12 @@ class Ip4Address(models.Model):
verbose_name_plural = 'IPv4 addresses'
@python_2_unicode_compatible
class HostType(models.Model):
host_type = models.CharField(max_length=64)
description = models.CharField(max_length=1024)
def __unicode__(self):
def __str__(self):
return self.host_type
def num_members(self):
@@ -466,6 +480,7 @@ class HostType(models.Model):
ordering = ("host_type",)
@python_2_unicode_compatible
class OperatingSystem(models.Model):
OS_ARCHITECTURES = (
('mips', _('MIPS')),
@@ -482,13 +497,14 @@ class OperatingSystem(models.Model):
version = models.CharField(max_length=64)
arch = models.CharField(max_length=255, choices=OS_ARCHITECTURES, blank=True, null=True)
def __unicode__(self):
def __str__(self):
return self.name + " " + self.version + " (" + self.arch + ")"
class Meta:
ordering = ("name", "version")
@python_2_unicode_compatible
class Host(models.Model):
location = models.CharField(max_length=1024)
brand = models.CharField(max_length=1024)
@@ -507,12 +523,15 @@ class Host(models.Model):
kerberos_principal_name = models.CharField(max_length=256, editable=False)
kerberos_principal_created_date = models.DateTimeField(null=True, blank=True, editable=False)
def __unicode__(self):
pxe_key = models.CharField(max_length=254, blank=True)
pxe_installable = models.BooleanField(default=False)
def __str__(self):
return self.hostname
def in_domain(self):
domains = self.interface_set.values_list('domain__domain_name', flat=True)
return u",".join(domains)
return ",".join(domains)
in_domain.short_description = "in domains"
@@ -524,39 +543,44 @@ class Host(models.Model):
def mac_addresses(self):
addresses = self.interface_set.filter(macaddr__isnull=False).values_list('macaddr', flat=True)
return ",".join(addresses)
return ", ".join(addresses)
def ip_addresses(self):
addresses = self.interface_set.filter(ip4address__isnull=False).values_list('ip4address__address', flat=True)
return ",".join(addresses)
return ", ".join(addresses)
def as_pxe_files(self):
return host_as_pxe_files(self)
@python_2_unicode_compatible
class Interface(models.Model):
name = models.CharField(max_length=128)
macaddr = models.CharField(max_length=17, validators=[validate_macaddr])
pxe_filename = models.CharField(max_length=64, blank=True)
dhcp_client = models.BooleanField(default=False)
host = models.ForeignKey(Host)
ip4address = models.ForeignKey(Ip4Address, blank=True, null=True, unique=True)
ip4address = models.OneToOneField(Ip4Address, blank=True, null=True)
created_date = models.DateTimeField(auto_now_add=True)
domain = models.ForeignKey(Domain)
def __unicode__(self):
def __str__(self):
return "%s (%s on %s)" % (self.macaddr, self.name, self.host.hostname)
def ipv6_enabled(self):
return self.ip6address_set.count() > 0
return self.ip6address_set.exists()
@python_2_unicode_compatible
class Ip6Address(models.Model):
subnet = models.ForeignKey(Ip6Subnet)
address = models.CharField(max_length=64)
address = models.GenericIPAddressField(protocol='IPv6')
interface = models.ForeignKey(Interface)
def full_address(self):
return self.subnet.network + self.address
def __unicode__(self):
def __str__(self):
return "%s (%s on %s)" % (
self.full_address(),
self.interface.name,
@@ -565,125 +589,3 @@ class Ip6Address(models.Model):
class Meta:
verbose_name = 'IPv6 address'
verbose_name_plural = 'IPv6 addresses'
def format_domain_serial_and_add_one(serial):
today = datetime.datetime.now()
res = re.findall("^%4d%02d%02d(\d\d)$" % (
today.year, today.month, today.day), str(serial), re.DOTALL)
if len(res) == 0:
""" This probably means that the serial is malformed
or the date is wrong. We assume that if the date is wrong,
it is in the past. Just create a new serial starting from 1."""
return "%4d%02d%02d%02d" % \
(today.year, today.month, today.day, 1)
elif len(res) == 1:
""" The serial contains todays date, just update it. """
try:
number = int(res[0])
except:
number = 1
if number >= 99:
""" This is bad... Just keep the number on 99.
We also send a mail to sysadmins telling them that
something is wrong..."""
else:
number += 1
return "%4d%02d%02d%02d" % (
today.year, today.month, today.day, number)
else:
""" Just return the first serial for today. """
return "%4d%02d%02d%02d" % \
(today.year, today.month, today.day, 1)
@receiver(post_save, sender=Ip4Subnet)
def create_ips_for_subnet(sender, instance, created, **kwargs):
if not created:
return
subnet = ipaddr.IPv4Network(instance.network + "/" + instance.netmask)
for addr in subnet.iterhosts():
address = Ip4Address(address=str(addr), subnet=instance)
address.save()
@receiver(pre_delete, sender=Ip4Subnet)
def delete_ips_for_subnet(sender, instance, **kwargs):
for addr in instance.ip4address_set.all():
addr.delete()
@receiver(pre_save, sender=Ip4Subnet)
def set_domain_name_for_subnet(sender, instance, **kwargs):
# we assume that the reverse domain_name does not change
if len(instance.domain_name) == 0:
ipspl = instance.network.split(".")
rev = "%s.%s.%s" % (ipspl[2], ipspl[1], ipspl[0])
instance.domain_name = "%s.in-addr.arpa" % rev
# update it's own serial
# if instance.domain_serial is not None:
# instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial)
# lets update the serial of the dhcp config
# when the subnet is changed
if instance.dhcp_config:
# instance.dhcp_config.serial = instance.dhcp_config.serial + 1
instance.dhcp_config.serial = format_domain_serial_and_add_one(instance.dhcp_config.serial)
instance.dhcp_config.save()
@receiver(pre_save, sender=Ip6Subnet)
def set_domain_name_for_ipv6_subnet(sender, instance, **kwargs):
if len(instance.domain_name) > 0:
return
network = ipaddr.IPv6Address("%s::" % instance.network)
instance.domain_name = ".".join(network.exploded.replace(":", "")[:16])[::-1] + ".ip6.arpa"
@receiver(post_save, sender=Interface)
def update_domain_serial_when_change_to_interface(sender, instance, created, **kwargs):
if instance.domain is not None:
domain = instance.domain
domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial)
domain.save()
if instance.ip4address is not None:
subnet = instance.ip4address.subnet
subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial)
subnet.save()
@receiver(post_save, sender=Host)
def update_domain_serial_when_change_to_host(sender, instance, created, **kwargs):
for interface in instance.interface_set.all():
if interface.domain is not None:
domain = interface.domain
domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial)
domain.save()
if interface.ip4address is not None:
subnet = interface.ip4address.subnet
subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial)
subnet.save()
@receiver(pre_delete, sender=Interface)
def update_domain_serial_when_interface_deleted(sender, instance, **kwargs):
if instance.domain is not None:
domain = instance.domain
domain.domain_serial += 1
domain.save()
if instance.ip4address is not None:
subnet = instance.ip4address.subnet
subnet.domain_serial += 1
subnet.save()
# @receiver(pre_save, sender=Domain)
# def update_domain_serial_when_domain_is_saved(sender, instance, **kwargs):
# instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial)
-191
View File
@@ -1,191 +0,0 @@
# Django settings for dns_mdb project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('System Administrator', 'sysadmin@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'mdb.db', # Or path to database file if using sqlite3.
'USER': 'mdb', # Not used with sqlite3.
'PASSWORD': 'mdb', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '3306', # Set to empty string for default. Not used with sqlite3.
}
}
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'Europe/Oslo'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/media/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = '@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
ROOT_URLCONF = 'urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'mdb',
'suit',
'django.contrib.admin',
)
# Defines a test runner
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
EMAIL_SUBJECT_PREFIX = '[MDB] '
EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 25
# for django-suit
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
TEMPLATE_CONTEXT_PROCESSORS = TCP + (
'django.core.context_processors.request',
)
# django-suit configuration
SUIT_CONFIG = {
# header
'ADMIN_NAME': 'MDB',
'HEADER_DATE_FORMAT': 'l, Y-m-d',
'HEADER_TIME_FORMAT': 'H:i',
# forms
# 'SHOW_REQUIRED_ASTERISK': True, # Default True
# 'CONFIRM_UNSAVED_CHANGES': True, # Default True
# menu
'SEARCH_URL': '/admin/mdb/host/',
# 'MENU_ICONS': {
# 'sites': 'icon-leaf',
# 'auth': 'icon-lock',
# },
# 'MENU_OPEN_FIRST_CHILD': True, # Default True
# 'MENU_EXCLUDE': ('auth.group',),
'MENU': (
{'app': 'mdb', 'label': 'MDB', 'url': 'mdb.host'},
{
'label': 'Settings',
'icon': 'icon-cog',
'models': ('auth.user', 'auth.group')
},
),
# misc
'LIST_PER_PAGE': 30
}
+140
View File
@@ -0,0 +1,140 @@
import logging
import os
import uuid
from django.conf import settings
from django.db.models.signals import pre_delete, post_save, pre_save
from django.dispatch import receiver
import ipaddress
from mdb.models import Interface, Host, Ip6Subnet, Ip4Subnet, Ip4Address
from mdb.utils import format_domain_serial_and_add_one
logger = logging.getLogger(__name__)
@receiver(post_save, sender=Ip4Subnet)
def create_ips_for_subnet(sender, instance, created, **kwargs):
if not created:
return
subnet = ipaddress.IPv4Network(instance.network + "/" + instance.netmask)
for addr in subnet.hosts():
address = Ip4Address(address=str(addr), subnet=instance)
address.save()
@receiver(pre_delete, sender=Ip4Subnet)
def delete_ips_for_subnet(sender, instance, **kwargs):
for addr in instance.ip4address_set.all():
addr.delete()
@receiver(pre_save, sender=Ip4Subnet)
def set_domain_name_for_subnet(sender, instance, **kwargs):
# we assume that the reverse domain_name does not change
if len(instance.domain_name) == 0:
ipspl = instance.network.split(".")
rev = "%s.%s.%s" % (ipspl[2], ipspl[1], ipspl[0])
instance.domain_name = "%s.in-addr.arpa" % rev
# update it's own serial
# if instance.domain_serial is not None:
# instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial)
# lets update the serial of the dhcp config
# when the subnet is changed
if instance.dhcp_config:
# instance.dhcp_config.serial = instance.dhcp_config.serial + 1
instance.dhcp_config.serial = format_domain_serial_and_add_one(instance.dhcp_config.serial)
instance.dhcp_config.save()
@receiver(pre_save, sender=Ip6Subnet)
def set_domain_name_for_ipv6_subnet(sender, instance, **kwargs):
if len(instance.domain_name) > 0:
return
network = ipaddress.IPv6Address("%s::" % instance.network)
instance.domain_name = ".".join(network.exploded.replace(":", "")[:16])[::-1] + ".ip6.arpa"
@receiver(post_save, sender=Interface)
def update_domain_serial_when_change_to_interface(sender, instance, created, **kwargs):
if instance.domain is not None:
domain = instance.domain
domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial)
domain.save()
if instance.ip4address is not None:
subnet = instance.ip4address.subnet
subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial)
subnet.save()
@receiver(post_save, sender=Host)
def update_domain_serial_when_change_to_host(sender, instance, created, **kwargs):
for interface in instance.interface_set.all():
if interface.domain is not None:
domain = interface.domain
domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial)
domain.save()
if interface.ip4address is not None:
subnet = interface.ip4address.subnet
subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial)
subnet.save()
@receiver(post_save, sender=Host)
def create_pxe_key_and_write_pxe_files_when_host_changes(sender, instance, created, **kwargs):
host = instance
if not host.pxe_key:
# generate key (prevent infinite recursion by using update)
pxe_key = uuid.uuid4().hex
Host.objects.filter(pk=host.pk).update(pxe_key=pxe_key)
host.pxe_key = pxe_key
for pxe_file_name, pxe_file in host.as_pxe_files():
path = os.path.join(settings.MDB_PXE_TFTP_ROOT, pxe_file_name)
if host.pxe_installable:
with open(path, 'w+') as f:
f.write(pxe_file)
logger.info("Created or updated {}".format(path))
# Also set necessary fields for pxe installation
interfaces = host.interface_set.exclude(ip4address__isnull=True)
for _if in interfaces:
changed = False
if not _if.pxe_filename:
_if.pxe_filename = 'pxelinux.0' # don't overwrite
changed = True
if not _if.dhcp_client:
_if.dhcp_client = True
changed = True
if changed:
_if.save()
else:
if os.path.exists(path):
os.unlink(path)
logger.info("deleted {}".format(path))
@receiver(pre_delete, sender=Interface)
def update_domain_serial_when_interface_deleted(sender, instance, **kwargs):
if instance.domain is not None:
domain = instance.domain
domain.domain_serial += 1
domain.save()
if instance.ip4address is not None:
subnet = instance.ip4address.subnet
subnet.domain_serial += 1
subnet.save()
# @receiver(pre_save, sender=Domain)
# def update_domain_serial_when_domain_is_saved(sender, instance, **kwargs):
# instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial)
+4
View File
@@ -0,0 +1,4 @@
kernel {{kernel}}
append ramdisk_size=14984 locale=en_US console-setup/ask_detect=false keyboard-configuration/layoutcode=no netcfg/wireless_wep= netcfg_choose_interface=eth0 netcfg/get_hostname= preseed/url={{preseed_config_url}} vga=normal initrd={{initrd}} -- snop={{host.pxe_key}}
prompt 0
timeout 0
+53 -13
View File
@@ -1,16 +1,56 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
import io
from django.contrib.auth.models import User
from django.core.management import call_command
from django.core.urlresolvers import reverse
from django.test import TestCase
from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase
from mdb.models import Host
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
class MyAPITestCases(APITestCase):
fixtures = ['test_data']
def create_user_with_token(self):
self.user = User.objects.create(username='admin')
self.token = Token.objects.create(user=self.user)
self.host = Host.objects.first()
def setUp(self):
self.create_user_with_token()
return super(MyAPITestCases, self).setUp()
def test_validate_puppet_host_by_secret(self):
data = {
'pxe_key': self.host.pxe_key,
'certname': '{}.{}'.format(self.host.hostname, self.host.interface_set.first().domain.domain_name)
}
self.client.credentials(HTTP_AUTHORIZATION='Token {}'.format(self.token.key))
res = self.client.post(reverse('api-validate-host-secret'), data, format='json')
self.assertEquals(res.status_code, 200, res.content)
self.assertFalse(Host.objects.get(pk=self.host.pk).pxe_installable)
class UnitTests(TestCase):
fixtures = ['test_data']
def test_host_as_pxe_files(self):
host = Host.objects.first()
self.assertEqual(len(host.as_pxe_files()), 1)
class RunManagementCommands(TestCase):
fixtures = ['test_data']
def test_zone_sync(self):
expected_zone_file = ''
with io.BytesIO() as f:
call_command('zone-sync', debug=True, stdout=f)
# TODO check expected_zone_file
def test_dhcp_sync(self):
expected_dhcp_file = ''
with io.BytesIO() as f:
call_command('dhcp-sync', ('--debug',), stdout=f)
# TODO check expected_dhcp_file
+81
View File
@@ -0,0 +1,81 @@
import datetime
from django.conf import settings
from django.template.loader import render_to_string
import ipaddress
import re
def format_domain_serial_and_add_one(serial):
today = datetime.datetime.now()
res = re.findall("^%4d%02d%02d(\d\d)$" % (
today.year, today.month, today.day), str(serial), re.DOTALL)
if len(res) == 0:
""" This probably means that the serial is malformed
or the date is wrong. We assume that if the date is wrong,
it is in the past. Just create a new serial starting from 1."""
return "%4d%02d%02d%02d" % \
(today.year, today.month, today.day, 1)
elif len(res) == 1:
""" The serial contains todays date, just update it. """
try:
number = int(res[0])
except:
number = 1
if number >= 99:
""" This is bad... Just keep the number on 99.
We also send a mail to sysadmins telling them that
something is wrong..."""
else:
number += 1
return "%4d%02d%02d%02d" % (
today.year, today.month, today.day, number)
else:
""" Just return the first serial for today. """
return "%4d%02d%02d%02d" % \
(today.year, today.month, today.day, 1)
def render_pxelinux_cfg(_if, host):
# TODO add fields to os model and make dynamic per host
distro = 'ubuntu/14_04/amd64/alternate/'
kernel = distro + 'linux'
initrd = distro + 'initrd.gz'
preseed_config_url = settings.MDB_PXE_PRESEED_URL
context = {
'kernel': kernel,
'initrd': initrd,
'preseed_config_url': preseed_config_url,
'host': host,
'interface': _if
}
return render_to_string('pxelinux.cfg', context=context)
def get_pxe_filename(interface, filename_format='mac_addr'):
"""
Mac or IP address based PXE filename
Ref: http://www.syslinux.org/wiki/index.php/PXELINUX#Examples
"""
ip_addr_in_hex = '{:02X}'.format(int(ipaddress.IPv4Address(interface.ip4address.address)))
formats = {
'ip_addr': ip_addr_in_hex,
'mac_addr': '01-{}'.format(interface.macaddr.lower().replace(':', '-'))
}
return formats.get(filename_format, formats['mac_addr'])
def host_as_pxe_files(host):
""" Returns a list of filename,content for each host interface with an IPv4 address """
from mdb.models import Host
assert isinstance(host, Host)
pxe_files = []
for _if in host.interface_set.exclude(ip4address__isnull=True):
filename = get_pxe_filename(_if)
content = render_pxelinux_cfg(_if, host)
pxe_files.append((filename, content))
return pxe_files
+3 -2
View File
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.core.validators import RegexValidator
import re
@@ -5,11 +6,11 @@ import re
hostname_re = re.compile(r'^(?!-)[-a-z0-9]+(?<!-)$', re.IGNORECASE)
validate_hostname = RegexValidator(
regex=hostname_re,
message=u'Enter a valid hostname',
message='Enter a valid hostname',
code='invalid')
macaddr_re = re.compile(r'^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$', re.IGNORECASE)
validate_macaddr = RegexValidator(
regex=macaddr_re,
message=u'Enter a valid MAC address',
message='Enter a valid MAC address',
code='invalid')
File renamed without changes.
+131
View File
@@ -0,0 +1,131 @@
import os
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('System Administrator', 'sysadmin@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'db.sqlite3',
}
}
TIME_ZONE = 'Europe/Oslo'
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
USE_I18N = True
USE_L10N = True
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
ADMIN_MEDIA_PREFIX = '/media/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = '@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl'
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
ROOT_URLCONF = 'mdbsite.urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'suit',
'django.contrib.admin',
'rest_framework',
'rest_framework.authtoken',
'mdb',
)
EMAIL_SUBJECT_PREFIX = '[MDB] '
TEMPLATE_CONTEXT_PROCESSORS = TCP + (
'django.core.context_processors.request', # for django-suit
)
# django-suit configuration
SUIT_CONFIG = {
# header
'ADMIN_NAME': 'MDB',
'HEADER_DATE_FORMAT': 'l, Y-m-d',
'HEADER_TIME_FORMAT': 'H:i',
# forms
# 'SHOW_REQUIRED_ASTERISK': True, # Default True
# 'CONFIRM_UNSAVED_CHANGES': True, # Default True
# menu
'SEARCH_URL': '/admin/mdb/host/',
# 'MENU_ICONS': {
# 'sites': 'icon-leaf',
# 'auth': 'icon-lock',
# },
# 'MENU_OPEN_FIRST_CHILD': True, # Default True
# 'MENU_EXCLUDE': ('auth.group',),
'MENU': (
{'app': 'mdb', 'label': 'MDB', 'url': 'mdb.host'},
{
'label': 'Settings',
'icon': 'icon-cog',
'models': ('auth.user', 'auth.group')
},
),
# misc
'LIST_PER_PAGE': 30
}
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',),
'PAGINATE_BY': 10
}
MDB_PXE_TFTP_ROOT = '/var/lib/tftpboot/pxelinux/pxelinux.cfg/'
MDB_PXE_PRESEED_URL = 'http://158.36.190.194/ubuntu/preseed_1404.cfg'
try:
from .local_settings import *
except ImportError:
pass
+15
View File
@@ -0,0 +1,15 @@
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.views.generic.base import RedirectView
from mdb.api.views import HostPXEValidate
admin.autodiscover()
urlpatterns = [
url(r'^$', RedirectView.as_view(url='admin/', permanent=True)),
url(r'^admin/', include(admin.site.urls)),
url(r'^api/validate-host-secret/', HostPXEValidate.as_view(), name='api-validate-host-secret')
]
urlpatterns += staticfiles_urlpatterns()
View File
File renamed without changes.
+6 -4
View File
@@ -1,5 +1,7 @@
Django>=1.7,<1.8
MySQL-python==1.2.5
ipaddr==2.1.11
Django>=1.8,<1.9
django-auth-ldap
django-suit
django-extensions==1.5.7
djangorestframework==3.2.4
django-suit==0.2.15
ipaddress==1.0.14
mysqlclient==1.3.6
-14
View File
@@ -1,14 +0,0 @@
from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.views.generic.base import RedirectView
admin.autodiscover()
urlpatterns = patterns(
'',
url(r'^$', RedirectView.as_view(url='admin/')),
url(r'^admin/', include(admin.site.urls)),
)
urlpatterns += staticfiles_urlpatterns()