mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
@ -83,6 +83,34 @@ Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce uni
|
||||
|
||||
---
|
||||
|
||||
## LOGGING
|
||||
|
||||
By default, all messages of INFO severity or higher will be logged to the console. Additionally, if `DEBUG` is False and email access has been configured, ERROR and CRITICAL messages will be emailed to the users defined in `ADMINS`.
|
||||
|
||||
The Django framework on which NetBox runs allows for the customization of logging, e.g. to write logs to file. Please consult the [Django logging documentation](https://docs.djangoproject.com/en/1.11/topics/logging/) for more information on configuring this setting. Below is an example which will write all INFO and higher messages to a file:
|
||||
|
||||
```
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': '/var/log/netbox.log',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['file'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## LOGIN_REQUIRED
|
||||
|
||||
Default: False
|
||||
|
@ -28,6 +28,9 @@ Create a file in the same directory as `configuration.py` (typically `netbox/net
|
||||
|
||||
## General Server Configuration
|
||||
|
||||
!!! info
|
||||
When using Windows Server 2012 you may need to specify a port on `AUTH_LDAP_SERVER_URI`. Use `3269` for secure, or `3268` for non-secure.
|
||||
|
||||
```python
|
||||
import ldap
|
||||
|
||||
@ -49,11 +52,11 @@ AUTH_LDAP_BIND_PASSWORD = "demo"
|
||||
LDAP_IGNORE_CERT_ERRORS = True
|
||||
```
|
||||
|
||||
!!! info
|
||||
When using Windows Server 2012 you may need to specify a port on AUTH_LDAP_SERVER_URI - 3269 for secure, 3268 for non-secure.
|
||||
|
||||
## User Authentication
|
||||
|
||||
!!! info
|
||||
When using Windows Server, `2012 AUTH_LDAP_USER_DN_TEMPLATE` should be set to None.
|
||||
|
||||
```python
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
@ -73,9 +76,6 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
When using Windows Server 2012 AUTH_LDAP_USER_DN_TEMPLATE should be set to None.
|
||||
|
||||
# User Groups for Permissions
|
||||
|
||||
```python
|
||||
@ -109,12 +109,11 @@ AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
||||
* `is_staff` - Users mapped to this group are enabled for access to the administration tools; this is the equivalent of checking the "staff status" box on a manually created user. This doesn't grant any specific permissions.
|
||||
* `is_superuser` - Users mapped to this group will be granted superuser status. Superusers are implicitly granted all permissions.
|
||||
|
||||
!!! info
|
||||
It is also possible map user attributes to Django attributes:
|
||||
It is also possible map user attributes to Django attributes:
|
||||
|
||||
```no-highlight
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"first_name": "givenName",
|
||||
"last_name": "sn"
|
||||
}
|
||||
```
|
||||
```python
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"first_name": "givenName",
|
||||
"last_name": "sn",
|
||||
}
|
||||
```
|
||||
|
@ -52,6 +52,13 @@ Once the new code is in place, run the upgrade script (which may need to be run
|
||||
# ./upgrade.sh
|
||||
```
|
||||
|
||||
!!! warning
|
||||
The upgrade script will prefer Python3 and pip3 if both executables are available. To force it to use Python2 and pip, use the `-2` argument as below.
|
||||
|
||||
```no-highlight
|
||||
# ./upgrade.sh -2
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
* Installs or upgrades any new required Python packages
|
||||
|
@ -52,6 +52,8 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
|
||||
comments = models.TextField(blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url']
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@ -107,6 +109,8 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
|
||||
comments = models.TextField(blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
csv_headers = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['provider', 'cid']
|
||||
unique_together = ['provider', 'cid']
|
||||
|
@ -280,6 +280,10 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
objects = SiteManager()
|
||||
|
||||
csv_headers = [
|
||||
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@ -402,6 +406,10 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
objects = RackManager()
|
||||
|
||||
csv_headers = [
|
||||
'site', 'group_name', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['site', 'name']
|
||||
unique_together = [
|
||||
@ -981,6 +989,11 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
objects = DeviceManager()
|
||||
|
||||
csv_headers = [
|
||||
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
|
||||
'site', 'rack_group', 'rack_name', 'position', 'face',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = ['rack', 'position', 'face']
|
||||
@ -1096,6 +1109,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
|
||||
self.asset_tag,
|
||||
self.get_status_display(),
|
||||
self.site.name,
|
||||
self.rack.group.name if self.rack and self.rack.group else None,
|
||||
self.rack.name if self.rack else None,
|
||||
self.position,
|
||||
self.get_face_display(),
|
||||
@ -1162,6 +1176,8 @@ class ConsolePort(models.Model):
|
||||
verbose_name='Console server port', blank=True, null=True)
|
||||
connection_status = models.NullBooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED)
|
||||
|
||||
csv_headers = ['console_server', 'cs_port', 'device', 'console_port', 'connection_status']
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'name']
|
||||
unique_together = ['device', 'name']
|
||||
@ -1231,6 +1247,8 @@ class PowerPort(models.Model):
|
||||
blank=True, null=True)
|
||||
connection_status = models.NullBooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED)
|
||||
|
||||
csv_headers = ['pdu', 'power_outlet', 'device', 'power_port', 'connection_status']
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'name']
|
||||
unique_together = ['device', 'name']
|
||||
@ -1392,6 +1410,8 @@ class InterfaceConnection(models.Model):
|
||||
connection_status = models.BooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED,
|
||||
verbose_name='Status')
|
||||
|
||||
csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status']
|
||||
|
||||
def clean(self):
|
||||
try:
|
||||
if self.interface_a == self.interface_b:
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
from copy import deepcopy
|
||||
from difflib import SequenceMatcher
|
||||
import re
|
||||
from natsort import natsorted
|
||||
from operator import attrgetter
|
||||
@ -776,20 +777,14 @@ class DeviceView(View):
|
||||
services = Service.objects.filter(device=device)
|
||||
secrets = device.secrets.all()
|
||||
|
||||
# Find any related devices for convenient linking in the UI
|
||||
related_devices = []
|
||||
if device.name:
|
||||
if re.match('.+[0-9]+$', device.name):
|
||||
# Strip 1 or more trailing digits (e.g. core-switch1)
|
||||
base_name = re.match('(.*?)[0-9]+$', device.name).group(1)
|
||||
elif re.match('.+\d[a-z]$', device.name.lower()):
|
||||
# Strip a trailing letter if preceded by a digit (e.g. dist-switch3a -> dist-switch3)
|
||||
base_name = re.match('(.*\d+)[a-z]$', device.name.lower()).group(1)
|
||||
else:
|
||||
base_name = None
|
||||
if base_name:
|
||||
related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk)\
|
||||
.select_related('rack', 'device_type__manufacturer')[:10]
|
||||
# Find up to ten devices in the same site with the same functional role for quick reference.
|
||||
related_devices = Device.objects.filter(
|
||||
site=device.site, device_role=device.device_role
|
||||
).exclude(
|
||||
pk=device.pk
|
||||
).select_related(
|
||||
'rack', 'device_type__manufacturer'
|
||||
)[:10]
|
||||
|
||||
# Show graph button on interfaces only if at least one graph has been created.
|
||||
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists()
|
||||
|
62
netbox/extras/management/commands/nbshell.py
Normal file
62
netbox/extras/management/commands/nbshell.py
Normal file
@ -0,0 +1,62 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import code
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from django import get_version
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Model
|
||||
|
||||
|
||||
APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users']
|
||||
|
||||
BANNER_TEXT = """### NetBox interactive shell ({node})
|
||||
### Python {python} | Django {django} | NetBox {netbox}
|
||||
### lsmodels() will show available models. Use help(<model>) for more info.""".format(
|
||||
node=platform.node(),
|
||||
python=platform.python_version(),
|
||||
django=get_version(),
|
||||
netbox=settings.VERSION
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Start the Django shell with all NetBox models already imported"
|
||||
django_models = {}
|
||||
|
||||
def _lsmodels(self):
|
||||
for app, models in self.django_models.items():
|
||||
app_name = apps.get_app_config(app).verbose_name
|
||||
print('{}:'.format(app_name))
|
||||
for m in models:
|
||||
print(' {}'.format(m))
|
||||
|
||||
def get_namespace(self):
|
||||
namespace = {}
|
||||
|
||||
# Gather Django models from each app
|
||||
for app in APPS:
|
||||
self.django_models[app] = []
|
||||
app_models = sys.modules['{}.models'.format(app)]
|
||||
for name in dir(app_models):
|
||||
model = getattr(app_models, name)
|
||||
try:
|
||||
if issubclass(model, Model):
|
||||
namespace[name] = model
|
||||
self.django_models[app].append(name)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# Load convenience commands
|
||||
namespace.update({
|
||||
'lsmodels': self._lsmodels,
|
||||
})
|
||||
|
||||
return namespace
|
||||
|
||||
def handle(self, **options):
|
||||
shell = code.interact(banner=BANNER_TEXT, local=self.get_namespace())
|
||||
return shell
|
@ -180,6 +180,18 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
required=False,
|
||||
label='Site',
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'vlan_group', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
vlan_group = ChainedModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
chains=(
|
||||
('site', 'site'),
|
||||
),
|
||||
required=False,
|
||||
label='VLAN group',
|
||||
widget=APISelect(
|
||||
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
|
||||
attrs={'filter-for': 'vlan', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
@ -187,11 +199,12 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
queryset=VLAN.objects.all(),
|
||||
chains=(
|
||||
('site', 'site'),
|
||||
('group', 'vlan_group'),
|
||||
),
|
||||
required=False,
|
||||
label='VLAN',
|
||||
widget=APISelect(
|
||||
api_url='/api/ipam/vlans/?site_id={{site}}', display_field='display_name'
|
||||
api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}', display_field='display_name'
|
||||
)
|
||||
)
|
||||
|
||||
@ -200,6 +213,14 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
fields = ['prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Initialize helper selectors
|
||||
instance = kwargs.get('instance')
|
||||
initial = kwargs.get('initial', {})
|
||||
if instance and instance.vlan is not None:
|
||||
initial['vlan_group'] = instance.vlan.group
|
||||
kwargs['initial'] = initial
|
||||
|
||||
super(PrefixForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['vrf'].empty_label = 'Global'
|
||||
|
@ -89,6 +89,8 @@ class VRF(CreatedUpdatedModel, CustomFieldModel):
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = 'VRF'
|
||||
@ -146,6 +148,8 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
csv_headers = ['prefix', 'rir', 'date_added', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['family', 'prefix']
|
||||
|
||||
@ -200,7 +204,7 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
def get_utilization(self):
|
||||
"""
|
||||
Determine the utilization rate of the aggregate prefix and return it as a percentage.
|
||||
Determine the prefix utilization of the aggregate and return it as a percentage.
|
||||
"""
|
||||
child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
|
||||
# Remove overlapping prefixes from list of children
|
||||
@ -297,6 +301,10 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
objects = PrefixQuerySet.as_manager()
|
||||
|
||||
csv_headers = [
|
||||
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['vrf', 'family', 'prefix']
|
||||
verbose_name_plural = 'prefixes'
|
||||
@ -307,9 +315,6 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse('ipam:prefix', args=[self.pk])
|
||||
|
||||
def get_duplicates(self):
|
||||
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
|
||||
|
||||
def clean(self):
|
||||
|
||||
if self.prefix:
|
||||
@ -357,6 +362,22 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
||||
self.description,
|
||||
])
|
||||
|
||||
def get_status_class(self):
|
||||
return STATUS_CHOICE_CLASSES[self.status]
|
||||
|
||||
def get_duplicates(self):
|
||||
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
|
||||
|
||||
def get_utilization(self):
|
||||
"""
|
||||
Determine the utilization of the prefix and return it as a percentage.
|
||||
"""
|
||||
child_count = IPAddress.objects.filter(address__net_contained_or_equal=str(self.prefix), vrf=self.vrf).count()
|
||||
prefix_size = self.prefix.size
|
||||
if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
|
||||
prefix_size -= 2
|
||||
return int(float(child_count) / prefix_size * 100)
|
||||
|
||||
@property
|
||||
def new_subnet(self):
|
||||
if self.family == 4:
|
||||
@ -368,9 +389,6 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
||||
return IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
|
||||
return None
|
||||
|
||||
def get_status_class(self):
|
||||
return STATUS_CHOICE_CLASSES[self.status]
|
||||
|
||||
|
||||
class IPAddressManager(models.Manager):
|
||||
|
||||
@ -414,6 +432,8 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
objects = IPAddressManager()
|
||||
|
||||
csv_headers = ['address', 'vrf', 'tenant', 'status', 'device', 'interface_name', 'is_primary', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['family', 'address']
|
||||
verbose_name = 'IP address'
|
||||
@ -452,11 +472,12 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
||||
def to_csv(self):
|
||||
|
||||
# Determine if this IP is primary for a Device
|
||||
is_primary = False
|
||||
if self.family == 4 and getattr(self, 'primary_ip4_for', False):
|
||||
is_primary = True
|
||||
elif self.family == 6 and getattr(self, 'primary_ip6_for', False):
|
||||
is_primary = True
|
||||
else:
|
||||
is_primary = False
|
||||
|
||||
return csv_format([
|
||||
self.address,
|
||||
@ -527,6 +548,8 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['site', 'group', 'vid']
|
||||
unique_together = [
|
||||
|
@ -34,7 +34,7 @@ RIR_ACTIONS = """
|
||||
|
||||
UTILIZATION_GRAPH = """
|
||||
{% load helpers %}
|
||||
{% utilization_graph value %}
|
||||
{% if record.pk %}{% utilization_graph value %}{% else %}—{% endif %}
|
||||
"""
|
||||
|
||||
ROLE_ACTIONS = """
|
||||
@ -241,6 +241,7 @@ class PrefixTable(BaseTable):
|
||||
prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
|
||||
status = tables.TemplateColumn(STATUS_LABEL)
|
||||
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
|
||||
get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='IP Usage')
|
||||
tenant = tables.TemplateColumn(TENANT_LINK)
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||
vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
|
||||
@ -248,7 +249,7 @@ class PrefixTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Prefix
|
||||
fields = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
|
||||
fields = ('pk', 'prefix', 'status', 'vrf', 'get_utilization', 'tenant', 'site', 'vlan', 'role', 'description')
|
||||
row_attrs = {
|
||||
'class': lambda record: 'success' if not record.pk else '',
|
||||
}
|
||||
|
@ -58,6 +58,11 @@ CORS_ORIGIN_REGEX_WHITELIST = [
|
||||
# r'^(https?://)?(\w+\.)?example\.com$',
|
||||
]
|
||||
|
||||
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
|
||||
# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
|
||||
# on a production system.
|
||||
DEBUG = False
|
||||
|
||||
# Email settings
|
||||
EMAIL = {
|
||||
'SERVER': 'localhost',
|
||||
@ -72,6 +77,10 @@ EMAIL = {
|
||||
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
|
||||
ENFORCE_GLOBAL_UNIQUE = False
|
||||
|
||||
# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
|
||||
# https://docs.djangoproject.com/en/1.11/topics/logging/
|
||||
LOGGING = {}
|
||||
|
||||
# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
|
||||
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
||||
LOGIN_REQUIRED = False
|
||||
|
@ -13,9 +13,9 @@ except ImportError:
|
||||
)
|
||||
|
||||
|
||||
VERSION = '2.0.5'
|
||||
VERSION = '2.0.6'
|
||||
|
||||
# Import local configuration
|
||||
# Import required configuration parameters
|
||||
ALLOWED_HOSTS = DATABASE = SECRET_KEY = None
|
||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||
try:
|
||||
@ -25,33 +25,35 @@ for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||
"Mandatory setting {} is missing from configuration.py.".format(setting)
|
||||
)
|
||||
|
||||
# Default configurations
|
||||
# Import optional configuration parameters
|
||||
ADMINS = getattr(configuration, 'ADMINS', [])
|
||||
DEBUG = getattr(configuration, 'DEBUG', False)
|
||||
EMAIL = getattr(configuration, 'EMAIL', {})
|
||||
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
|
||||
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
|
||||
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
|
||||
BASE_PATH = getattr(configuration, 'BASE_PATH', '')
|
||||
if BASE_PATH:
|
||||
BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only
|
||||
CORS_ORIGIN_ALLOW_ALL = getattr(configuration, 'CORS_ORIGIN_ALLOW_ALL', False)
|
||||
CORS_ORIGIN_REGEX_WHITELIST = getattr(configuration, 'CORS_ORIGIN_REGEX_WHITELIST', [])
|
||||
CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', [])
|
||||
DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y')
|
||||
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
||||
DEBUG = getattr(configuration, 'DEBUG', False)
|
||||
ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
|
||||
EMAIL = getattr(configuration, 'EMAIL', {})
|
||||
LOGGING = getattr(configuration, 'LOGGING', {})
|
||||
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
|
||||
MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False)
|
||||
MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000)
|
||||
PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
|
||||
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
|
||||
NETBOX_USERNAME = getattr(configuration, 'NETBOX_USERNAME', '')
|
||||
NETBOX_PASSWORD = getattr(configuration, 'NETBOX_PASSWORD', '')
|
||||
TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC')
|
||||
DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y')
|
||||
SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
|
||||
TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
|
||||
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
|
||||
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
||||
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
||||
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
|
||||
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
|
||||
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
|
||||
ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
|
||||
MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000)
|
||||
CORS_ORIGIN_ALLOW_ALL = getattr(configuration, 'CORS_ORIGIN_ALLOW_ALL', False)
|
||||
CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', [])
|
||||
CORS_ORIGIN_REGEX_WHITELIST = getattr(configuration, 'CORS_ORIGIN_REGEX_WHITELIST', [])
|
||||
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
|
||||
TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
|
||||
TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC')
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
|
||||
|
||||
# Attempt to import LDAP configuration if it has been defined
|
||||
|
@ -291,6 +291,7 @@ class Secret(CreatedUpdatedModel):
|
||||
hash = models.CharField(max_length=128, editable=False)
|
||||
|
||||
plaintext = None
|
||||
csv_headers = ['device', 'role', 'name', 'plaintext']
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'role', 'name']
|
||||
|
@ -57,6 +57,12 @@
|
||||
<a href="{% url 'ipam:aggregate_list' %}?rir={{ aggregate.rir.slug }}">{{ aggregate.rir }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Utilization</td>
|
||||
<td>
|
||||
{{ aggregate.get_utilization }}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Date Added</td>
|
||||
<td>
|
||||
|
@ -121,8 +121,8 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP Addresses</td>
|
||||
<td><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">{{ ipaddress_count }}</a></td>
|
||||
<td>Utilization</td>
|
||||
<td><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">{{ ipaddress_count }} IP addresses</a> ({{ prefix.get_utilization }}%)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -8,13 +8,19 @@
|
||||
{% render_field form.prefix %}
|
||||
{% render_field form.status %}
|
||||
{% render_field form.vrf %}
|
||||
{% render_field form.site %}
|
||||
{% render_field form.vlan %}
|
||||
{% render_field form.role %}
|
||||
{% render_field form.description %}
|
||||
{% render_field form.is_pool %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Site/VLAN Assignment</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.site %}
|
||||
{% render_field form.vlan_group %}
|
||||
{% render_field form.vlan %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Tenancy</strong></div>
|
||||
<div class="panel-body">
|
||||
|
@ -41,6 +41,8 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
|
||||
comments = models.TextField(blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
csv_headers = ['name', 'slug', 'group', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['group', 'name']
|
||||
|
||||
|
@ -478,8 +478,8 @@ class ChainedFieldsMixin(forms.BaseForm):
|
||||
|
||||
filters_dict = {}
|
||||
for (db_field, parent_field) in field.chains:
|
||||
if self.is_bound and self.data.get(parent_field):
|
||||
filters_dict[db_field] = self.data[parent_field]
|
||||
if self.is_bound and parent_field in self.data:
|
||||
filters_dict[db_field] = self.data[parent_field] or None
|
||||
elif self.initial.get(parent_field):
|
||||
filters_dict[db_field] = self.initial[parent_field]
|
||||
elif self.fields[parent_field].widget.attrs.get('nullable'):
|
||||
|
@ -102,7 +102,9 @@ class ObjectListView(View):
|
||||
.format(et.name))
|
||||
# Fall back to built-in CSV export
|
||||
elif 'export' in request.GET and hasattr(model, 'to_csv'):
|
||||
output = '\n'.join([obj.to_csv() for obj in self.queryset])
|
||||
headers = getattr(model, 'csv_headers', None)
|
||||
output = ','.join(headers) + '\n' if headers else ''
|
||||
output += '\n'.join([obj.to_csv() for obj in self.queryset])
|
||||
response = HttpResponse(
|
||||
output,
|
||||
content_type='text/csv'
|
||||
|
25
upgrade.sh
25
upgrade.sh
@ -5,6 +5,25 @@
|
||||
# Once the script completes, remember to restart the WSGI service (e.g.
|
||||
# gunicorn or uWSGI).
|
||||
|
||||
# Determine which version of Python/pip to use. Default to v3 (if available)
|
||||
# but allow the user to force v2.
|
||||
PYTHON="python3"
|
||||
PIP="pip3"
|
||||
type $PYTHON >/dev/null 2>&1 && type $PIP >/dev/null 2>&1 || PYTHON="python" PIP="pip"
|
||||
while getopts ":2" opt; do
|
||||
case $opt in
|
||||
2)
|
||||
PYTHON="python"
|
||||
PIP="pip"
|
||||
echo "Forcing Python/pip v2"
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Optionally use sudo if not already root, and always prompt for password
|
||||
# before running the command
|
||||
PREFIX="sudo -k "
|
||||
@ -20,12 +39,6 @@ COMMAND="${PREFIX}find . -name \"*.pyc\" -delete"
|
||||
echo "Cleaning up stale Python bytecode ($COMMAND)..."
|
||||
eval $COMMAND
|
||||
|
||||
# Prefer python3/pip3
|
||||
PYTHON="python3"
|
||||
type $PYTHON >/dev/null 2>&1 || PYTHON="python"
|
||||
PIP="pip3"
|
||||
type $PIP >/dev/null 2>&1 || PIP="pip"
|
||||
|
||||
# Install any new Python packages
|
||||
COMMAND="${PREFIX}${PIP} install -r requirements.txt --upgrade"
|
||||
echo "Updating required Python packages ($COMMAND)..."
|
||||
|
Reference in New Issue
Block a user