mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into develop-2.4
This commit is contained in:
3
.github/ISSUE_TEMPLATE.md
vendored
3
.github/ISSUE_TEMPLATE.md
vendored
@ -21,6 +21,7 @@
|
|||||||
[ ] Feature request <!-- An enhancement of existing functionality -->
|
[ ] Feature request <!-- An enhancement of existing functionality -->
|
||||||
[ ] Bug report <!-- Unexpected or erroneous behavior -->
|
[ ] Bug report <!-- Unexpected or erroneous behavior -->
|
||||||
[ ] Documentation <!-- A modification to the documentation -->
|
[ ] Documentation <!-- A modification to the documentation -->
|
||||||
|
[ ] Housekeeping <!-- Changes pertaining to the codebase itself -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please describe the environment in which you are running NetBox. (Be sure
|
Please describe the environment in which you are running NetBox. (Be sure
|
||||||
@ -31,7 +32,7 @@
|
|||||||
-->
|
-->
|
||||||
### Environment
|
### Environment
|
||||||
* Python version: <!-- Example: 3.5.4 -->
|
* Python version: <!-- Example: 3.5.4 -->
|
||||||
* NetBox version: <!-- Example: 2.1.3 -->
|
* NetBox version: <!-- Example: 2.3.5 -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
BUG REPORTS must include:
|
BUG REPORTS must include:
|
||||||
|
@ -9,7 +9,7 @@ python:
|
|||||||
- "3.5"
|
- "3.5"
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- pip install pep8
|
- pip install pycodestyle
|
||||||
before_script:
|
before_script:
|
||||||
- psql --version
|
- psql --version
|
||||||
- psql -U postgres -c 'SELECT version();'
|
- psql -U postgres -c 'SELECT version();'
|
||||||
|
@ -19,6 +19,7 @@ from . import serializers
|
|||||||
|
|
||||||
class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
|
class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
|
||||||
fields = (
|
fields = (
|
||||||
|
(Circuit, ['status']),
|
||||||
(CircuitTermination, ['term_side']),
|
(CircuitTermination, ['term_side']),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,11 +36,12 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
|||||||
fields = (
|
fields = (
|
||||||
(Device, ['face', 'status']),
|
(Device, ['face', 'status']),
|
||||||
(ConsolePort, ['connection_status']),
|
(ConsolePort, ['connection_status']),
|
||||||
(Interface, ['form_factor']),
|
(Interface, ['form_factor', 'mode']),
|
||||||
(InterfaceConnection, ['connection_status']),
|
(InterfaceConnection, ['connection_status']),
|
||||||
(InterfaceTemplate, ['form_factor']),
|
(InterfaceTemplate, ['form_factor']),
|
||||||
(PowerPort, ['connection_status']),
|
(PowerPort, ['connection_status']),
|
||||||
(Rack, ['type', 'width']),
|
(Rack, ['type', 'width']),
|
||||||
|
(Site, ['status']),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ from .models import (
|
|||||||
RackRole, Region, Site, VirtualChassis
|
RackRole, Region, Site, VirtualChassis
|
||||||
)
|
)
|
||||||
|
|
||||||
DEVICE_BY_PK_RE = '{\d+\}'
|
DEVICE_BY_PK_RE = r'{\d+\}'
|
||||||
|
|
||||||
INTERFACE_MODE_HELP_TEXT = """
|
INTERFACE_MODE_HELP_TEXT = """
|
||||||
Access: One untagged VLAN<br />
|
Access: One untagged VLAN<br />
|
||||||
|
@ -1352,6 +1352,12 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
'face': "Must specify rack face when defining rack position.",
|
'face': "Must specify rack face when defining rack position.",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Prevent 0U devices from being assigned to a specific position
|
||||||
|
if self.position and self.device_type.u_height == 0:
|
||||||
|
raise ValidationError({
|
||||||
|
'position': "A U0 device type ({}) cannot be assigned to a rack position.".format(self.device_type)
|
||||||
|
})
|
||||||
|
|
||||||
if self.rack:
|
if self.rack:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1612,8 +1618,8 @@ class ConsoleServerPortManager(models.Manager):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Pad any trailing digits to effect natural sorting
|
# Pad any trailing digits to effect natural sorting
|
||||||
return super(ConsoleServerPortManager, self).get_queryset().extra(select={
|
return super(ConsoleServerPortManager, self).get_queryset().extra(select={
|
||||||
'name_padded': "CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), "
|
'name_padded': r"CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), "
|
||||||
"LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))",
|
r"LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))",
|
||||||
}).order_by('device', 'name_padded')
|
}).order_by('device', 'name_padded')
|
||||||
|
|
||||||
|
|
||||||
@ -1720,8 +1726,8 @@ class PowerOutletManager(models.Manager):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Pad any trailing digits to effect natural sorting
|
# Pad any trailing digits to effect natural sorting
|
||||||
return super(PowerOutletManager, self).get_queryset().extra(select={
|
return super(PowerOutletManager, self).get_queryset().extra(select={
|
||||||
'name_padded': "CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), "
|
'name_padded': r"CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), "
|
||||||
"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))",
|
r"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))",
|
||||||
}).order_by('device', 'name_padded')
|
}).order_by('device', 'name_padded')
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ from tenancy.tables import COL_TENANT
|
|||||||
from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
|
from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, InventoryItem, Manufacturer, Platform,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, InventoryItem,
|
||||||
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site,
|
Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
VirtualChassis,
|
RackReservation, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
|
|
||||||
REGION_LINK = """
|
REGION_LINK = """
|
||||||
@ -621,7 +621,7 @@ class InterfaceConnectionTable(BaseTable):
|
|||||||
interface_b = tables.Column(verbose_name='Interface B')
|
interface_b = tables.Column(verbose_name='Interface B')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Interface
|
model = InterfaceConnection
|
||||||
fields = ('device_a', 'interface_a', 'device_b', 'interface_b')
|
fields = ('device_a', 'interface_a', 'device_b', 'interface_b')
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class TopologyMapViewSet(ModelViewSet):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
data = tmap.render(img_format=img_format)
|
data = tmap.render(img_format=img_format)
|
||||||
except:
|
except Exception:
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
"There was an error generating the requested graph. Ensure that the GraphViz executables have been "
|
"There was an error generating the requested graph. Ensure that the GraphViz executables have been "
|
||||||
"installed correctly."
|
"installed correctly."
|
||||||
|
@ -5,6 +5,7 @@ from collections import OrderedDict
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from mptt.forms import TreeNodeMultipleChoiceField
|
from mptt.forms import TreeNodeMultipleChoiceField
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
@ -64,7 +65,14 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
||||||
if not cf.required or bulk_edit or filterable_only:
|
if not cf.required or bulk_edit or filterable_only:
|
||||||
choices = [(None, '---------')] + choices
|
choices = [(None, '---------')] + choices
|
||||||
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)
|
# Check for a default choice
|
||||||
|
default_choice = None
|
||||||
|
if initial:
|
||||||
|
try:
|
||||||
|
default_choice = cf.choices.get(value=initial).pk
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
|
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
|
||||||
|
|
||||||
# URL
|
# URL
|
||||||
elif cf.type == CF_TYPE_URL:
|
elif cf.type == CF_TYPE_URL:
|
||||||
|
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='customfield',
|
model_name='customfield',
|
||||||
name='default',
|
name='default',
|
||||||
field=models.CharField(blank=True, help_text='Default value for the field. Use "true" or "false" for booleans. N/A for selection fields.', max_length=100),
|
field=models.CharField(blank=True, help_text='Default value for the field. Use "true" or "false" for booleans.', max_length=100),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='customfield',
|
model_name='customfield',
|
||||||
|
@ -19,7 +19,7 @@ def verify_postgresql_version(apps, schema_editor):
|
|||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute("SELECT VERSION()")
|
cursor.execute("SELECT VERSION()")
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
pg_version = re.match('^PostgreSQL (\d+\.\d+(\.\d+)?)', row[0]).group(1)
|
pg_version = re.match(r'^PostgreSQL (\d+\.\d+(\.\d+)?)', row[0]).group(1)
|
||||||
if StrictVersion(pg_version) < StrictVersion('9.4.0'):
|
if StrictVersion(pg_version) < StrictVersion('9.4.0'):
|
||||||
raise Exception("PostgreSQL 9.4.0 or higher is required ({} found). Upgrade PostgreSQL and then run migrations again.".format(pg_version))
|
raise Exception("PostgreSQL 9.4.0 or higher is required ({} found). Upgrade PostgreSQL and then run migrations again.".format(pg_version))
|
||||||
|
|
||||||
|
@ -172,8 +172,7 @@ class CustomField(models.Model):
|
|||||||
default = models.CharField(
|
default = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text='Default value for the field. Use "true" or "false" for '
|
help_text='Default value for the field. Use "true" or "false" for booleans.'
|
||||||
'booleans. N/A for selection fields.'
|
|
||||||
)
|
)
|
||||||
weight = models.PositiveSmallIntegerField(
|
weight = models.PositiveSmallIntegerField(
|
||||||
default=100,
|
default=100,
|
||||||
|
@ -163,8 +163,8 @@ class IOSSSH(SSHClient):
|
|||||||
|
|
||||||
sh_ver = self._send('show version').split('\r\n')
|
sh_ver = self._send('show version').split('\r\n')
|
||||||
return {
|
return {
|
||||||
'serial': parse(sh_ver, 'Processor board ID ([^\s]+)'),
|
'serial': parse(sh_ver, r'Processor board ID ([^\s]+)'),
|
||||||
'description': parse(sh_ver, 'cisco ([^\s]+)')
|
'description': parse(sh_ver, r'cisco ([^\s]+)')
|
||||||
}
|
}
|
||||||
|
|
||||||
def items(chassis_serial=None):
|
def items(chassis_serial=None):
|
||||||
@ -172,9 +172,9 @@ class IOSSSH(SSHClient):
|
|||||||
for i in cmd:
|
for i in cmd:
|
||||||
i_fmt = i.replace('\r\n', ' ')
|
i_fmt = i.replace('\r\n', ' ')
|
||||||
try:
|
try:
|
||||||
m_name = re.search('NAME: "([^"]+)"', i_fmt).group(1)
|
m_name = re.search(r'NAME: "([^"]+)"', i_fmt).group(1)
|
||||||
m_pid = re.search('PID: ([^\s]+)', i_fmt).group(1)
|
m_pid = re.search(r'PID: ([^\s]+)', i_fmt).group(1)
|
||||||
m_serial = re.search('SN: ([^\s]+)', i_fmt).group(1)
|
m_serial = re.search(r'SN: ([^\s]+)', i_fmt).group(1)
|
||||||
# Omit built-in items and those with no PID
|
# Omit built-in items and those with no PID
|
||||||
if m_serial != chassis_serial and m_pid.lower() != 'unspecified':
|
if m_serial != chassis_serial and m_pid.lower() != 'unspecified':
|
||||||
yield {
|
yield {
|
||||||
@ -208,7 +208,7 @@ class OpengearSSH(SSHClient):
|
|||||||
try:
|
try:
|
||||||
stdin, stdout, stderr = self.ssh.exec_command("showserial")
|
stdin, stdout, stderr = self.ssh.exec_command("showserial")
|
||||||
serial = stdout.readlines()[0].strip()
|
serial = stdout.readlines()[0].strip()
|
||||||
except:
|
except Exception:
|
||||||
raise RuntimeError("Failed to glean chassis serial from device.")
|
raise RuntimeError("Failed to glean chassis serial from device.")
|
||||||
# Older models don't provide serial info
|
# Older models don't provide serial info
|
||||||
if serial == "No serial number information available":
|
if serial == "No serial number information available":
|
||||||
@ -217,7 +217,7 @@ class OpengearSSH(SSHClient):
|
|||||||
try:
|
try:
|
||||||
stdin, stdout, stderr = self.ssh.exec_command("config -g config.system.model")
|
stdin, stdout, stderr = self.ssh.exec_command("config -g config.system.model")
|
||||||
description = stdout.readlines()[0].split(' ', 1)[1].strip()
|
description = stdout.readlines()[0].split(' ', 1)[1].strip()
|
||||||
except:
|
except Exception:
|
||||||
raise RuntimeError("Failed to glean chassis description from device.")
|
raise RuntimeError("Failed to glean chassis description from device.")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -95,7 +95,31 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
requested_prefixes = request.data if isinstance(request.data, list) else [request.data]
|
requested_prefixes = request.data if isinstance(request.data, list) else [request.data]
|
||||||
|
|
||||||
# Allocate prefixes to the requested objects based on availability within the parent
|
# Allocate prefixes to the requested objects based on availability within the parent
|
||||||
for requested_prefix in requested_prefixes:
|
for i, requested_prefix in enumerate(requested_prefixes):
|
||||||
|
|
||||||
|
# Validate requested prefix size
|
||||||
|
error_msg = None
|
||||||
|
if 'prefix_length' not in requested_prefix:
|
||||||
|
error_msg = "Item {}: prefix_length field missing".format(i)
|
||||||
|
elif not isinstance(requested_prefix['prefix_length'], int):
|
||||||
|
error_msg = "Item {}: Invalid prefix length ({})".format(
|
||||||
|
i, requested_prefix['prefix_length']
|
||||||
|
)
|
||||||
|
elif prefix.family == 4 and requested_prefix['prefix_length'] > 32:
|
||||||
|
error_msg = "Item {}: Invalid prefix length ({}) for IPv4".format(
|
||||||
|
i, requested_prefix['prefix_length']
|
||||||
|
)
|
||||||
|
elif prefix.family == 6 and requested_prefix['prefix_length'] > 128:
|
||||||
|
error_msg = "Item {}: Invalid prefix length ({}) for IPv6".format(
|
||||||
|
i, requested_prefix['prefix_length']
|
||||||
|
)
|
||||||
|
if error_msg:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"detail": error_msg
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
# Find the first available prefix equal to or larger than the requested size
|
# Find the first available prefix equal to or larger than the requested size
|
||||||
for available_prefix in available_prefixes.iter_cidrs():
|
for available_prefix in available_prefixes.iter_cidrs():
|
||||||
@ -157,8 +181,8 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
requested_ips = request.data if isinstance(request.data, list) else [request.data]
|
requested_ips = request.data if isinstance(request.data, list) else [request.data]
|
||||||
|
|
||||||
# Determine if the requested number of IPs is available
|
# Determine if the requested number of IPs is available
|
||||||
available_ips = list(prefix.get_available_ips())
|
available_ips = prefix.get_available_ips()
|
||||||
if len(available_ips) < len(requested_ips):
|
if available_ips.size < len(requested_ips):
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"detail": "An insufficient number of IP addresses are available within the prefix {} ({} "
|
"detail": "An insufficient number of IP addresses are available within the prefix {} ({} "
|
||||||
@ -168,8 +192,9 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Assign addresses from the list of available IPs and copy VRF assignment from the parent prefix
|
# Assign addresses from the list of available IPs and copy VRF assignment from the parent prefix
|
||||||
|
available_ips = iter(available_ips)
|
||||||
for requested_ip in requested_ips:
|
for requested_ip in requested_ips:
|
||||||
requested_ip['address'] = available_ips.pop(0)
|
requested_ip['address'] = next(available_ips)
|
||||||
requested_ip['vrf'] = prefix.vrf.pk if prefix.vrf else None
|
requested_ip['vrf'] = prefix.vrf.pk if prefix.vrf else None
|
||||||
|
|
||||||
# Initialize the serializer with a list or a single object depending on what was requested
|
# Initialize the serializer with a list or a single object depending on what was requested
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
import netaddr
|
import netaddr
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
@ -242,6 +243,10 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
method='search_by_parent',
|
method='search_by_parent',
|
||||||
label='Parent prefix',
|
label='Parent prefix',
|
||||||
)
|
)
|
||||||
|
address = django_filters.CharFilter(
|
||||||
|
method='filter_address',
|
||||||
|
label='Address',
|
||||||
|
)
|
||||||
mask_length = django_filters.NumberFilter(
|
mask_length = django_filters.NumberFilter(
|
||||||
method='filter_mask_length',
|
method='filter_mask_length',
|
||||||
label='Mask length',
|
label='Mask length',
|
||||||
@ -325,6 +330,17 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
def filter_address(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
try:
|
||||||
|
# Match address and subnet mask
|
||||||
|
if '/' in value:
|
||||||
|
return queryset.filter(address=value)
|
||||||
|
return queryset.filter(address__net_host=value)
|
||||||
|
except ValidationError:
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
def filter_mask_length(self, queryset, name, value):
|
def filter_mask_length(self, queryset, name, value):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
|
@ -294,7 +294,15 @@ SWAGGER_SETTINGS = {
|
|||||||
'utilities.custom_inspectors.NullablePaginatorInspector',
|
'utilities.custom_inspectors.NullablePaginatorInspector',
|
||||||
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||||
]
|
],
|
||||||
|
'SECURITY_DEFINITIONS': {
|
||||||
|
'Bearer': {
|
||||||
|
'type': 'apiKey',
|
||||||
|
'name': 'Authorization',
|
||||||
|
'in': 'header',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'VALIDATOR_URL': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -307,5 +315,5 @@ INTERNAL_IPS = (
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
HOSTNAME = socket.gethostname()
|
HOSTNAME = socket.gethostname()
|
||||||
except:
|
except Exception:
|
||||||
HOSTNAME = 'localhost'
|
HOSTNAME = 'localhost'
|
||||||
|
@ -52,9 +52,9 @@ _patterns = [
|
|||||||
url(r'^api/secrets/', include('secrets.api.urls')),
|
url(r'^api/secrets/', include('secrets.api.urls')),
|
||||||
url(r'^api/tenancy/', include('tenancy.api.urls')),
|
url(r'^api/tenancy/', include('tenancy.api.urls')),
|
||||||
url(r'^api/virtualization/', include('virtualization.api.urls')),
|
url(r'^api/virtualization/', include('virtualization.api.urls')),
|
||||||
url(r'^api/docs/$', schema_view.with_ui('swagger', cache_timeout=None), name='api_docs'),
|
url(r'^api/docs/$', schema_view.with_ui('swagger'), name='api_docs'),
|
||||||
url(r'^api/redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='api_redocs'),
|
url(r'^api/redoc/$', schema_view.with_ui('redoc'), name='api_redocs'),
|
||||||
url(r'^api/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema_swagger'),
|
url(r'^api/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(), name='schema_swagger'),
|
||||||
|
|
||||||
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
||||||
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||||
|
@ -27,7 +27,7 @@ def validate_rsa_key(key, is_secret=True):
|
|||||||
raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.")
|
raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.")
|
||||||
try:
|
try:
|
||||||
PKCS1_OAEP.new(key)
|
PKCS1_OAEP.new(key)
|
||||||
except:
|
except Exception:
|
||||||
raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.")
|
raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.")
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class UserKey(models.Model):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'public_key': "Invalid RSA key format."
|
'public_key': "Invalid RSA key format."
|
||||||
})
|
})
|
||||||
except:
|
except Exception:
|
||||||
raise ValidationError("Something went wrong while trying to save your key. Please ensure that you're "
|
raise ValidationError("Something went wrong while trying to save your key. Please ensure that you're "
|
||||||
"uploading a valid RSA public key in PEM format (no SSH/PGP).")
|
"uploading a valid RSA public key in PEM format (no SSH/PGP).")
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<li class="occupied h{{ u.device.device_type.u_height }}u"{% ifequal u.device.face face_id %} style="background-color: #{{ u.device.device_role.color }}"{% endifequal %}>
|
<li class="occupied h{{ u.device.device_type.u_height }}u"{% ifequal u.device.face face_id %} style="background-color: #{{ u.device.device_role.color }}"{% endifequal %}>
|
||||||
{% ifequal u.device.face face_id %}
|
{% ifequal u.device.face face_id %}
|
||||||
<a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
|
<a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
|
||||||
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type.full_name }} ({{ u.device.device_type.u_height }}U){% if u.device.asset_tag %}<br />{{ u.device.asset_tag }}{% endif %}">
|
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type.full_name }} ({{ u.device.device_type.u_height }}U){% if u.device.asset_tag %}<br />{{ u.device.asset_tag }}{% endif %}{% if u.device.serial %}<br />{{ u.device.serial }}{% endif %}">
|
||||||
{{ u.device.name|default:u.device.device_role }}
|
{{ u.device.name|default:u.device.device_role }}
|
||||||
{% if u.device.devicebay_count %}
|
{% if u.device.devicebay_count %}
|
||||||
({{ u.device.get_children.count }}/{{ u.device.devicebay_count }})
|
({{ u.device.get_children.count }}/{{ u.device.devicebay_count }})
|
||||||
|
@ -38,10 +38,10 @@ COLOR_CHOICES = (
|
|||||||
('607d8b', 'Dark grey'),
|
('607d8b', 'Dark grey'),
|
||||||
('111111', 'Black'),
|
('111111', 'Black'),
|
||||||
)
|
)
|
||||||
NUMERIC_EXPANSION_PATTERN = '\[((?:\d+[?:,-])+\d+)\]'
|
NUMERIC_EXPANSION_PATTERN = r'\[((?:\d+[?:,-])+\d+)\]'
|
||||||
ALPHANUMERIC_EXPANSION_PATTERN = '\[((?:[a-zA-Z0-9]+[?:,-])+[a-zA-Z0-9]+)\]'
|
ALPHANUMERIC_EXPANSION_PATTERN = r'\[((?:[a-zA-Z0-9]+[?:,-])+[a-zA-Z0-9]+)\]'
|
||||||
IP4_EXPANSION_PATTERN = '\[((?:[0-9]{1,3}[?:,-])+[0-9]{1,3})\]'
|
IP4_EXPANSION_PATTERN = r'\[((?:[0-9]{1,3}[?:,-])+[0-9]{1,3})\]'
|
||||||
IP6_EXPANSION_PATTERN = '\[((?:[0-9a-f]{1,4}[?:,-])+[0-9a-f]{1,4})\]'
|
IP6_EXPANSION_PATTERN = r'\[((?:[0-9a-f]{1,4}[?:,-])+[0-9a-f]{1,4})\]'
|
||||||
|
|
||||||
|
|
||||||
def parse_numeric_range(string, base=10):
|
def parse_numeric_range(string, base=10):
|
||||||
@ -407,7 +407,7 @@ class FlexibleModelChoiceField(forms.ModelChoiceField):
|
|||||||
try:
|
try:
|
||||||
if not self.to_field_name:
|
if not self.to_field_name:
|
||||||
key = 'pk'
|
key = 'pk'
|
||||||
elif re.match('^\{\d+\}$', value):
|
elif re.match(r'^\{\d+\}$', value):
|
||||||
key = 'pk'
|
key = 'pk'
|
||||||
value = value.strip('{}')
|
value = value.strip('{}')
|
||||||
else:
|
else:
|
||||||
|
@ -15,7 +15,7 @@ class EnhancedURLValidator(URLValidator):
|
|||||||
A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1
|
A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1
|
||||||
"""
|
"""
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
if not item or not re.match('^[a-z][0-9a-z+\-.]*$', item.lower()):
|
if not item or not re.match(r'^[a-z][0-9a-z+\-.]*$', item.lower()):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -23,8 +23,11 @@ fi
|
|||||||
|
|
||||||
# Check all python source files for PEP 8 compliance, but explicitly
|
# Check all python source files for PEP 8 compliance, but explicitly
|
||||||
# ignore:
|
# ignore:
|
||||||
|
# - W504: line break after binary operator
|
||||||
# - E501: line greater than 80 characters in length
|
# - E501: line greater than 80 characters in length
|
||||||
pep8 --ignore=E501 netbox/
|
pycodestyle \
|
||||||
|
--ignore=W504,E501 \
|
||||||
|
netbox/
|
||||||
RC=$?
|
RC=$?
|
||||||
if [[ $RC != 0 ]]; then
|
if [[ $RC != 0 ]]; then
|
||||||
echo -e "\n$(info) one or more PEP 8 errors detected, failing build."
|
echo -e "\n$(info) one or more PEP 8 errors detected, failing build."
|
||||||
|
Reference in New Issue
Block a user