1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Basic Support for Python 3 (#827)

* Rudimentary python3 support

* update docs and trigger Travis

* fix some of the tests

* fix all python3 errors

* change env calls to just python

* add @python_2_unicode_compatible decorator to models for python2 compatibility

* switch netbox.configuration to from netbox import configuration
This commit is contained in:
Jens L
2017-01-23 22:44:29 +01:00
committed by Jeremy Stretch
parent 1bddd038fe
commit 80439c495e
17 changed files with 152 additions and 97 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.pyc
/netbox/netbox/configuration.py
/netbox/netbox/ldap_config.py
/netbox/static
.idea
/*.sh

View File

@ -9,6 +9,9 @@ env:
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
install:
- pip install -r requirements.txt
- pip install pep8

View File

@ -3,14 +3,14 @@
**Debian/Ubuntu**
```no-highlight
# apt-get install -y python2.7 python-dev python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
# apt-get install -y python3 python3-dev python3-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
```
**CentOS/RHEL**
```no-highlight
# yum install -y epel-release
# yum install -y gcc python2 python-devel python-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
# yum install -y gcc python3 python3-devel python3-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
```
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.

View File

@ -1,6 +1,7 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from dcim.fields import ASNField
from extras.models import CustomFieldModel, CustomFieldValue
@ -33,6 +34,7 @@ def humanize_speed(speed):
return '{} Kbps'.format(speed)
@python_2_unicode_compatible
class Provider(CreatedUpdatedModel, CustomFieldModel):
"""
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
@ -51,7 +53,7 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
@ -67,6 +69,7 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
])
@python_2_unicode_compatible
class CircuitType(models.Model):
"""
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
@ -78,13 +81,14 @@ class CircuitType(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug)
@python_2_unicode_compatible
class Circuit(CreatedUpdatedModel, CustomFieldModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
@ -105,7 +109,7 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
ordering = ['provider', 'cid']
unique_together = ['provider', 'cid']
def __unicode__(self):
def __str__(self):
return u'{} {}'.format(self.provider, self.cid)
def get_absolute_url(self):
@ -141,6 +145,7 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
commit_rate_human.admin_order_field = 'commit_rate'
@python_2_unicode_compatible
class CircuitTermination(models.Model):
circuit = models.ForeignKey('Circuit', related_name='terminations', on_delete=models.CASCADE)
term_side = models.CharField(max_length=1, choices=TERM_SIDE_CHOICES, verbose_name='Termination')
@ -156,7 +161,7 @@ class CircuitTermination(models.Model):
ordering = ['circuit', 'term_side']
unique_together = ['circuit', 'term_side']
def __unicode__(self):
def __str__(self):
return u'{} (Side {})'.format(self.circuit, self.get_term_side_display())
def get_peer_termination(self):

View File

@ -13,7 +13,7 @@ from utilities.forms import (
SlugField,
)
from formfields import MACAddressFormField
from .formfields import MACAddressFormField
from .models import (
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,

View File

@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Count, Q, ObjectDoesNotExist
from django.utils.encoding import python_2_unicode_compatible
from circuits.models import Circuit
from extras.models import CustomFieldModel, CustomField, CustomFieldValue
@ -199,6 +200,7 @@ class SiteManager(NaturalOrderByManager):
return self.natural_order_by('name')
@python_2_unicode_compatible
class Site(CreatedUpdatedModel, CustomFieldModel):
"""
A Site represents a geographic location within a network; typically a building or campus. The optional facility
@ -222,7 +224,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
@ -265,6 +267,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
# Racks
#
@python_2_unicode_compatible
class RackGroup(models.Model):
"""
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For
@ -282,13 +285,14 @@ class RackGroup(models.Model):
['site', 'slug'],
]
def __unicode__(self):
def __str__(self):
return u'{} - {}'.format(self.site.name, self.name)
def get_absolute_url(self):
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
@python_2_unicode_compatible
class RackRole(models.Model):
"""
Racks can be organized by functional role, similar to Devices.
@ -300,7 +304,7 @@ class RackRole(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
@ -313,6 +317,7 @@ class RackManager(NaturalOrderByManager):
return self.natural_order_by('site__name', 'name')
@python_2_unicode_compatible
class Rack(CreatedUpdatedModel, CustomFieldModel):
"""
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
@ -343,7 +348,7 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
['site', 'facility_id'],
]
def __unicode__(self):
def __str__(self):
return self.display_name
def get_absolute_url(self):
@ -442,7 +447,7 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
devices = self.devices.select_related('device_type').filter(position__gte=1).exclude(pk__in=exclude)
# Initialize the rack unit skeleton
units = range(1, self.u_height + 1)
units = list(range(1, self.u_height + 1))
# Remove units consumed by installed devices
for d in devices:
@ -477,6 +482,7 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
# Device Types
#
@python_2_unicode_compatible
class Manufacturer(models.Model):
"""
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
@ -487,13 +493,14 @@ class Manufacturer(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
return "{}?manufacturer={}".format(reverse('dcim:devicetype_list'), self.slug)
@python_2_unicode_compatible
class DeviceType(models.Model, CustomFieldModel):
"""
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
@ -538,7 +545,7 @@ class DeviceType(models.Model, CustomFieldModel):
['manufacturer', 'slug'],
]
def __unicode__(self):
def __str__(self):
return self.model
def __init__(self, *args, **kwargs):
@ -608,6 +615,7 @@ class DeviceType(models.Model, CustomFieldModel):
return bool(self.subdevice_role is False)
@python_2_unicode_compatible
class ConsolePortTemplate(models.Model):
"""
A template for a ConsolePort to be created for a new Device.
@ -619,10 +627,11 @@ class ConsolePortTemplate(models.Model):
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@python_2_unicode_compatible
class ConsoleServerPortTemplate(models.Model):
"""
A template for a ConsoleServerPort to be created for a new Device.
@ -634,10 +643,11 @@ class ConsoleServerPortTemplate(models.Model):
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@python_2_unicode_compatible
class PowerPortTemplate(models.Model):
"""
A template for a PowerPort to be created for a new Device.
@ -649,10 +659,11 @@ class PowerPortTemplate(models.Model):
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@python_2_unicode_compatible
class PowerOutletTemplate(models.Model):
"""
A template for a PowerOutlet to be created for a new Device.
@ -664,7 +675,7 @@ class PowerOutletTemplate(models.Model):
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@ -706,6 +717,7 @@ class InterfaceManager(models.Manager):
}).order_by(*ordering)
@python_2_unicode_compatible
class InterfaceTemplate(models.Model):
"""
A template for a physical data interface on a new Device.
@ -721,10 +733,11 @@ class InterfaceTemplate(models.Model):
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@python_2_unicode_compatible
class DeviceBayTemplate(models.Model):
"""
A template for a DeviceBay to be created for a new parent Device.
@ -736,7 +749,7 @@ class DeviceBayTemplate(models.Model):
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@ -744,6 +757,7 @@ class DeviceBayTemplate(models.Model):
# Devices
#
@python_2_unicode_compatible
class DeviceRole(models.Model):
"""
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
@ -756,13 +770,14 @@ class DeviceRole(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
return "{}?role={}".format(reverse('dcim:device_list'), self.slug)
@python_2_unicode_compatible
class Platform(models.Model):
"""
Platform refers to the software or firmware running on a Device; for example, "Cisco IOS-XR" or "Juniper Junos".
@ -776,7 +791,7 @@ class Platform(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
@ -789,6 +804,7 @@ class DeviceManager(NaturalOrderByManager):
return self.natural_order_by('name')
@python_2_unicode_compatible
class Device(CreatedUpdatedModel, CustomFieldModel):
"""
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
@ -828,7 +844,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
ordering = ['name']
unique_together = ['rack', 'position', 'face']
def __unicode__(self):
def __str__(self):
return self.display_name
def get_absolute_url(self):
@ -968,6 +984,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
return RPC_CLIENTS.get(self.platform.rpc_client)
@python_2_unicode_compatible
class ConsolePort(models.Model):
"""
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
@ -982,7 +999,7 @@ class ConsolePort(models.Model):
ordering = ['device', 'name']
unique_together = ['device', 'name']
def __unicode__(self):
def __str__(self):
return self.name
# Used for connections export
@ -1011,6 +1028,7 @@ class ConsoleServerPortManager(models.Manager):
}).order_by('device', 'name_as_integer')
@python_2_unicode_compatible
class ConsoleServerPort(models.Model):
"""
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
@ -1023,10 +1041,11 @@ class ConsoleServerPort(models.Model):
class Meta:
unique_together = ['device', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@python_2_unicode_compatible
class PowerPort(models.Model):
"""
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
@ -1041,7 +1060,7 @@ class PowerPort(models.Model):
ordering = ['device', 'name']
unique_together = ['device', 'name']
def __unicode__(self):
def __str__(self):
return self.name
# Used for connections export
@ -1064,6 +1083,7 @@ class PowerOutletManager(models.Manager):
}).order_by('device', 'name_padded')
@python_2_unicode_compatible
class PowerOutlet(models.Model):
"""
A physical power outlet (output) within a Device which provides power to a PowerPort.
@ -1076,10 +1096,11 @@ class PowerOutlet(models.Model):
class Meta:
unique_together = ['device', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@python_2_unicode_compatible
class Interface(models.Model):
"""
A physical data interface within a Device. An Interface can connect to exactly one other Interface via the creation
@ -1099,7 +1120,7 @@ class Interface(models.Model):
ordering = ['device', 'name']
unique_together = ['device', 'name']
def __unicode__(self):
def __str__(self):
return self.name
def clean(self):
@ -1176,6 +1197,7 @@ class InterfaceConnection(models.Model):
])
@python_2_unicode_compatible
class DeviceBay(models.Model):
"""
An empty space within a Device which can house a child device
@ -1189,7 +1211,7 @@ class DeviceBay(models.Model):
ordering = ['device', 'name']
unique_together = ['device', 'name']
def __unicode__(self):
def __str__(self):
return u'{} - {}'.format(self.device.name, self.name)
def clean(self):
@ -1205,6 +1227,7 @@ class DeviceBay(models.Model):
raise ValidationError("Cannot install a device into itself.")
@python_2_unicode_compatible
class Module(models.Model):
"""
A Module represents a piece of hardware within a Device, such as a line card or power supply. Modules are used only
@ -1223,5 +1246,5 @@ class Module(models.Model):
ordering = ['device__id', 'parent__id', 'name']
unique_together = ['device', 'parent', 'name']
def __unicode__(self):
def __str__(self):
return self.name

View File

@ -65,7 +65,7 @@ class SiteTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/sites/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -75,7 +75,7 @@ class SiteTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/sites/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -84,9 +84,9 @@ class SiteTest(APITestCase):
def test_get_site_list_rack(self, endpoint='/{}api/dcim/sites/1/racks/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in json.loads(response.content):
for i in json.loads(response.content.decode('utf-8')):
self.assertEqual(
sorted(i.keys()),
sorted(self.rack_fields),
@ -99,9 +99,9 @@ class SiteTest(APITestCase):
def test_get_site_list_graphs(self, endpoint='/{}api/dcim/sites/1/graphs/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in json.loads(response.content):
for i in json.loads(response.content.decode('utf-8')):
self.assertEqual(
sorted(i.keys()),
sorted(self.graph_fields),
@ -159,7 +159,7 @@ class RackTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/racks/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -173,7 +173,7 @@ class RackTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/racks/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -202,7 +202,7 @@ class ManufacturersTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/manufacturers/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -212,7 +212,7 @@ class ManufacturersTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/manufacturers/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -250,7 +250,7 @@ class DeviceTypeTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/device-types/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -261,7 +261,7 @@ class DeviceTypeTest(APITestCase):
def test_detail_list(self, endpoint='/{}api/dcim/device-types/1/'.format(settings.BASE_PATH)):
# TODO: details returns list view.
# response = self.client.get(endpoint)
# content = json.loads(response.content)
# content = json.loads(response.content.decode('utf-8'))
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(
# sorted(content.keys()),
@ -284,7 +284,7 @@ class DeviceRolesTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/device-roles/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -294,7 +294,7 @@ class DeviceRolesTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/device-roles/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -312,7 +312,7 @@ class PlatformsTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/platforms/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -322,7 +322,7 @@ class PlatformsTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/platforms/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -360,7 +360,7 @@ class DeviceTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/devices/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for device in content:
self.assertEqual(
@ -425,7 +425,7 @@ class DeviceTest(APITestCase):
]
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
device = content[0]
self.assertEqual(
@ -435,7 +435,7 @@ class DeviceTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/devices/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -453,7 +453,7 @@ class ConsoleServerPortsTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/devices/9/console-server-ports/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for console_port in content:
self.assertEqual(
@ -475,7 +475,7 @@ class ConsolePortsTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/devices/1/console-ports/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for console_port in content:
self.assertEqual(
@ -493,7 +493,7 @@ class ConsolePortsTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/console-ports/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -514,7 +514,7 @@ class PowerPortsTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/devices/1/power-ports/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -528,7 +528,7 @@ class PowerPortsTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/power-ports/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -549,7 +549,7 @@ class PowerOutletsTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/devices/11/power-outlets/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -599,7 +599,7 @@ class InterfaceTest(APITestCase):
def test_get_list(self, endpoint='/{}api/dcim/devices/1/interfaces/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
@ -613,7 +613,7 @@ class InterfaceTest(APITestCase):
def test_get_detail(self, endpoint='/{}api/dcim/interfaces/1/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -625,19 +625,19 @@ class InterfaceTest(APITestCase):
)
def test_get_graph_list(self, endpoint='/{}api/dcim/interfaces/1/graphs/'.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
sorted(i.keys()),
sorted(SiteTest.graph_fields),
)
response = self.client.get(endpoint)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
for i in content:
self.assertEqual(
sorted(i.keys()),
sorted(SiteTest.graph_fields),
)
def test_get_interface_connections(self, endpoint='/{}api/dcim/interface-connections/4/'
.format(settings.BASE_PATH)):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),
@ -659,7 +659,7 @@ class RelatedConnectionsTest(APITestCase):
def test_get_list(self, endpoint=('/{}api/dcim/related-connections/?peer-device=test1-edge1&peer-interface=xe-0/0/3'
.format(settings.BASE_PATH))):
response = self.client.get(endpoint)
content = json.loads(response.content)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
sorted(content.keys()),

View File

@ -50,7 +50,7 @@ class FlatJSONRenderer(renderers.BaseRenderer):
def render(self, data, media_type=None, renderer_context=None):
def flatten(entry):
for key, val in entry.iteritems():
for key, val in entry.items():
if isinstance(val, dict):
for child_key, child_val in flatten(val):
yield "{}_{}".format(key, child_key), child_val

View File

@ -8,6 +8,7 @@ from django.core.validators import ValidationError
from django.db import models
from django.http import HttpResponse
from django.template import Template, Context
from django.utils.encoding import python_2_unicode_compatible
from django.utils.safestring import mark_safe
@ -93,6 +94,7 @@ class CustomFieldModel(object):
return OrderedDict([(field, None) for field in fields])
@python_2_unicode_compatible
class CustomField(models.Model):
obj_type = models.ManyToManyField(ContentType, related_name='custom_fields', verbose_name='Object(s)',
limit_choices_to={'model__in': CUSTOMFIELD_MODELS},
@ -114,7 +116,7 @@ class CustomField(models.Model):
class Meta:
ordering = ['weight', 'name']
def __unicode__(self):
def __str__(self):
return self.label or self.name.replace('_', ' ').capitalize()
def serialize_value(self, value):
@ -153,6 +155,7 @@ class CustomField(models.Model):
return serialized_value
@python_2_unicode_compatible
class CustomFieldValue(models.Model):
field = models.ForeignKey('CustomField', related_name='values')
obj_type = models.ForeignKey(ContentType, related_name='+', on_delete=models.PROTECT)
@ -164,7 +167,7 @@ class CustomFieldValue(models.Model):
ordering = ['obj_type', 'obj_id']
unique_together = ['field', 'obj_type', 'obj_id']
def __unicode__(self):
def __str__(self):
return u'{} {}'.format(self.obj, self.field)
@property
@ -183,6 +186,7 @@ class CustomFieldValue(models.Model):
super(CustomFieldValue, self).save(*args, **kwargs)
@python_2_unicode_compatible
class CustomFieldChoice(models.Model):
field = models.ForeignKey('CustomField', related_name='choices', limit_choices_to={'type': CF_TYPE_SELECT},
on_delete=models.CASCADE)
@ -193,7 +197,7 @@ class CustomFieldChoice(models.Model):
ordering = ['field', 'weight', 'value']
unique_together = ['field', 'value']
def __unicode__(self):
def __str__(self):
return self.value
def clean(self):
@ -207,6 +211,7 @@ class CustomFieldChoice(models.Model):
CustomFieldValue.objects.filter(field__type=CF_TYPE_SELECT, serialized_value=str(pk)).delete()
@python_2_unicode_compatible
class Graph(models.Model):
type = models.PositiveSmallIntegerField(choices=GRAPH_TYPE_CHOICES)
weight = models.PositiveSmallIntegerField(default=1000)
@ -217,7 +222,7 @@ class Graph(models.Model):
class Meta:
ordering = ['type', 'weight', 'name']
def __unicode__(self):
def __str__(self):
return self.name
def embed_url(self, obj):
@ -231,6 +236,7 @@ class Graph(models.Model):
return template.render(Context({'obj': obj}))
@python_2_unicode_compatible
class ExportTemplate(models.Model):
content_type = models.ForeignKey(ContentType, limit_choices_to={'model__in': EXPORTTEMPLATE_MODELS})
name = models.CharField(max_length=100)
@ -245,7 +251,7 @@ class ExportTemplate(models.Model):
['content_type', 'name']
]
def __unicode__(self):
def __str__(self):
return u'{}: {}'.format(self.content_type, self.name)
def to_response(self, context_dict, filename):
@ -264,6 +270,7 @@ class ExportTemplate(models.Model):
return response
@python_2_unicode_compatible
class TopologyMap(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
@ -278,7 +285,7 @@ class TopologyMap(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
@property
@ -328,6 +335,7 @@ class UserActionManager(models.Manager):
self.log_bulk_action(user, content_type, ACTION_BULK_DELETE, message)
@python_2_unicode_compatible
class UserAction(models.Model):
"""
A record of an action (add, edit, or delete) performed on an object by a User.
@ -344,7 +352,7 @@ class UserAction(models.Model):
class Meta:
ordering = ['-time']
def __unicode__(self):
def __str__(self):
if self.message:
return u'{} {}'.format(self.user, self.message)
return u'{} {} {}'.format(self.user, self.get_action_display(), self.content_type)

View File

@ -1,8 +1,8 @@
#!/usr/bin/python
#!/usr/bin/env python
# This script will generate a random 50-character string suitable for use as a SECRET_KEY.
import os
import random
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(-_=+)'
random.seed = (os.urandom(2048))
print ''.join(random.choice(charset) for c in range(50))
print(''.join(random.choice(charset) for c in range(50)))

View File

@ -7,6 +7,7 @@ from django.core.urlresolvers import reverse
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models.expressions import RawSQL
from django.utils.encoding import python_2_unicode_compatible
from dcim.models import Interface
from extras.models import CustomFieldModel, CustomFieldValue
@ -72,6 +73,7 @@ IP_PROTOCOL_CHOICES = (
)
@python_2_unicode_compatible
class VRF(CreatedUpdatedModel, CustomFieldModel):
"""
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
@ -91,7 +93,7 @@ class VRF(CreatedUpdatedModel, CustomFieldModel):
verbose_name = 'VRF'
verbose_name_plural = 'VRFs'
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
@ -107,6 +109,7 @@ class VRF(CreatedUpdatedModel, CustomFieldModel):
])
@python_2_unicode_compatible
class RIR(models.Model):
"""
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
@ -122,13 +125,14 @@ class RIR(models.Model):
verbose_name = 'RIR'
verbose_name_plural = 'RIRs'
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
return "{}?rir={}".format(reverse('ipam:aggregate_list'), self.slug)
@python_2_unicode_compatible
class Aggregate(CreatedUpdatedModel, CustomFieldModel):
"""
An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
@ -144,7 +148,7 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
class Meta:
ordering = ['family', 'prefix']
def __unicode__(self):
def __str__(self):
return str(self.prefix)
def get_absolute_url(self):
@ -206,6 +210,7 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
return int(children_size / self.prefix.size * 100)
@python_2_unicode_compatible
class Role(models.Model):
"""
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
@ -218,7 +223,7 @@ class Role(models.Model):
class Meta:
ordering = ['weight', 'name']
def __unicode__(self):
def __str__(self):
return self.name
@property
@ -265,6 +270,7 @@ class PrefixQuerySet(NullsFirstQuerySet):
return filter(lambda p: p.depth <= limit, queryset)
@python_2_unicode_compatible
class Prefix(CreatedUpdatedModel, CustomFieldModel):
"""
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
@ -294,7 +300,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
ordering = ['vrf', 'family', 'prefix']
verbose_name_plural = 'prefixes'
def __unicode__(self):
def __str__(self):
return str(self.prefix)
def get_absolute_url(self):
@ -379,6 +385,7 @@ class IPAddressManager(models.Manager):
return qs.annotate(host=RawSQL('INET(HOST(ipam_ipaddress.address))', [])).order_by('family', 'host')
@python_2_unicode_compatible
class IPAddress(CreatedUpdatedModel, CustomFieldModel):
"""
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
@ -411,7 +418,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
verbose_name = 'IP address'
verbose_name_plural = 'IP addresses'
def __unicode__(self):
def __str__(self):
return str(self.address)
def get_absolute_url(self):
@ -471,6 +478,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
return STATUS_CHOICE_CLASSES[self.status]
@python_2_unicode_compatible
class VLANGroup(models.Model):
"""
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
@ -488,13 +496,14 @@ class VLANGroup(models.Model):
verbose_name = 'VLAN group'
verbose_name_plural = 'VLAN groups'
def __unicode__(self):
def __str__(self):
return u'{} - {}'.format(self.site.name, self.name)
def get_absolute_url(self):
return "{}?group_id={}".format(reverse('ipam:vlan_list'), self.pk)
@python_2_unicode_compatible
class VLAN(CreatedUpdatedModel, CustomFieldModel):
"""
A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
@ -526,7 +535,7 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
verbose_name = 'VLAN'
verbose_name_plural = 'VLANs'
def __unicode__(self):
def __str__(self):
return self.display_name
def get_absolute_url(self):
@ -560,6 +569,7 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
return STATUS_CHOICE_CLASSES[self.status]
@python_2_unicode_compatible
class Service(CreatedUpdatedModel):
"""
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device. A Service may optionally be tied
@ -578,5 +588,5 @@ class Service(CreatedUpdatedModel):
ordering = ['device', 'protocol', 'port']
unique_together = ['device', 'protocol', 'port']
def __unicode__(self):
def __str__(self):
return u'{} ({}/{})'.format(self.name, self.port, self.get_protocol_display())

View File

@ -6,7 +6,7 @@ from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured
try:
import configuration
from netbox import configuration
except ImportError:
raise ImproperlyConfigured("Configuration file is not present. Please define netbox/netbox/configuration.py per "
"the documentation.")
@ -50,7 +50,7 @@ CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# Attempt to import LDAP configuration if it has been defined
LDAP_IGNORE_CERT_ERRORS = False
try:
from ldap_config import *
from netbox.ldap_config import *
LDAP_CONFIGURED = True
except ImportError:
LDAP_CONFIGURED = False

View File

@ -2,7 +2,7 @@ from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
from views import home, handle_500, trigger_500
from netbox.views import home, handle_500, trigger_500
from users.views import login, logout

View File

@ -8,7 +8,7 @@ from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import force_bytes
from django.utils.encoding import force_bytes, python_2_unicode_compatible
from dcim.models import Device
from utilities.models import CreatedUpdatedModel
@ -51,6 +51,7 @@ class UserKeyQuerySet(models.QuerySet):
raise Exception("Bulk deletion has been disabled.")
@python_2_unicode_compatible
class UserKey(CreatedUpdatedModel):
"""
A UserKey stores a user's personal RSA (public) encryption key, which is used to generate their unique encrypted
@ -76,7 +77,7 @@ class UserKey(CreatedUpdatedModel):
self.__initial_public_key = self.public_key
self.__initial_master_key_cipher = self.master_key_cipher
def __unicode__(self):
def __str__(self):
return self.user.username
def clean(self, *args, **kwargs):
@ -170,6 +171,7 @@ class UserKey(CreatedUpdatedModel):
self.save()
@python_2_unicode_compatible
class SecretRole(models.Model):
"""
A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles
@ -186,7 +188,7 @@ class SecretRole(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
@ -201,6 +203,7 @@ class SecretRole(models.Model):
return user in self.users.all() or user.groups.filter(pk__in=self.groups.all()).exists()
@python_2_unicode_compatible
class Secret(CreatedUpdatedModel):
"""
A Secret stores an AES256-encrypted copy of sensitive data, such as passwords or secret keys. An irreversible
@ -227,7 +230,7 @@ class Secret(CreatedUpdatedModel):
self.plaintext = kwargs.pop('plaintext', None)
super(Secret, self).__init__(*args, **kwargs)
def __unicode__(self):
def __str__(self):
if self.role and self.device:
return u'{} for {}'.format(self.role, self.device)
return u'Secret'

View File

@ -1 +0,0 @@
from test_models import *

View File

@ -1,12 +1,14 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from extras.models import CustomFieldModel, CustomFieldValue
from utilities.models import CreatedUpdatedModel
from utilities.utils import csv_format
@python_2_unicode_compatible
class TenantGroup(models.Model):
"""
An arbitrary collection of Tenants.
@ -17,13 +19,14 @@ class TenantGroup(models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug)
@python_2_unicode_compatible
class Tenant(CreatedUpdatedModel, CustomFieldModel):
"""
A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal
@ -39,7 +42,7 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
class Meta:
ordering = ['group', 'name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):

View File

@ -10,7 +10,7 @@ from utilities.views import (
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
)
from models import Tenant, TenantGroup
from .models import Tenant, TenantGroup
from . import filters, forms, tables