Compare commits

...

1664 Commits

Author SHA1 Message Date
Jeremy Stretch
328958876a Merge pull request #2041 from digitalocean/develop
Release v2.3.3
2018-04-19 11:15:48 -04:00
Jeremy Stretch
a7389de109 Release v2.3.3 2018-04-19 11:07:19 -04:00
Jeremy Stretch
b911ab01d2 Merge pull request #2038 from DirtyCajunRice/develop
stop force value split w ArrayFieldSelectMultiple. Fixes #2037
2018-04-19 10:55:25 -04:00
Nicholas St. Germain
9153c71cbf stop force value split w ArrayFieldSelectMultiple 2018-04-18 14:02:40 -05:00
Jeremy Stretch
b44aa9d32e Fixes #2014: Allow assignment of VLANs to VM interfaces via the API 2018-04-18 12:37:20 -04:00
Jeremy Stretch
bcb1d9af0b Fixes #2012: Fixed deselection of an IP address as the primary IP for its parent device/VM 2018-04-12 13:03:20 -04:00
Jeremy Stretch
ef84889a57 Fixes #2022: Show 0 for zero-value fields on CSV export 2018-04-12 12:54:21 -04:00
Jeremy Stretch
81c027e7cf Fixes #2023: Manufacturer should not be a required field when importing platforms 2018-04-12 12:45:25 -04:00
Jeremy Stretch
fd62a248ee Merge pull request #2020 from Wikia/intfix
#2019 : avoid illegal casts on large integers
2018-04-12 12:06:44 -04:00
frankfarmer
2c8bea1b59 avoid illegal casts on large integers
A similar fix was applied in e5e32d82d00e454ba5edf25316828c1cdcd7673e
2018-04-09 17:42:54 -07:00
Jeremy Stretch
07364abf9e Fixes #1988: Order interfaces naturally when bulk renaming 2018-03-29 15:15:13 -04:00
Jeremy Stretch
20cb13e1bb Fixes #1975: Correct filtering logic for custom boolean fields 2018-03-29 14:47:35 -04:00
Jeremy Stretch
3f3b385de7 Fixes #1999: Added missing description field to site edit form 2018-03-29 13:49:50 -04:00
Jeremy Stretch
94b12e506e Fixes #1993: Corrected status choices in site CSV import form 2018-03-29 09:50:29 -04:00
Jeremy Stretch
4ec6e52e73 Closes #1990: Improved search function when assigning an IP address to an interface 2018-03-29 09:45:17 -04:00
Jeremy Stretch
88adc5ca86 Post-release version bump 2018-03-22 15:06:59 -04:00
Jeremy Stretch
68f73c7f94 Merge pull request #1987 from digitalocean/develop
Release v2.3.2
2018-03-22 15:05:59 -04:00
Jeremy Stretch
223c95adbc Release v2.3.2 2018-03-22 14:59:23 -04:00
Jeremy Stretch
3aaca1ca02 Require validation dependencies when installing drf-yasg 2018-03-22 11:51:27 -04:00
Jeremy Stretch
6a4d17b8a5 Merge pull request #1985 from lampwins/docs/apache-header
added X-Forwarded-Proto header to apache config
2018-03-22 11:43:43 -04:00
Jeremy Stretch
720c5fabaf Merge pull request #1643 from RyanBreaker/wildcard
Implements #1586, add additional variants for ExpandableNameFields
2018-03-22 11:40:54 -04:00
John Anderson
1c5239a4d0 added X-Forwarded-Proto header to apache config 2018-03-22 10:51:12 -04:00
Jeremy Stretch
05b5609d86 Merge pull request #1930 from davcamer/drf-yasg
Use drf_yasg to generate swagger
2018-03-21 15:43:05 -04:00
Jeremy Stretch
7e92aeb7ac Merge pull request #1981 from luto/patch-1
compare strings using "==" not "is", fix crash bug
2018-03-21 15:22:00 -04:00
Jeremy Stretch
6e2eb15a80 Fixes #1978: Include all virtual chassis member interfaces in LLDP neighbors view 2018-03-21 15:12:15 -04:00
luto
0b825ac3d0 compare strings using "==" not "is", fixes #1980 2018-03-21 14:28:59 +01:00
Dave Cameron
b5f1d74d6f Definition for /dcim/connected-device/ endpoint 2018-03-16 16:48:08 -04:00
Dave Cameron
e071b7dfd5 The id__in field is a csv-separated string of ids
drf_yasg is interpreting it as a number because NumericInFilter inherits
from django's NumberFilter which explicitly identifies as being a
DecimalField.
2018-03-15 17:07:58 -04:00
Dave Cameron
53e4e74930 Differentiate better between boolean and 0, 1 choices 2018-03-15 17:07:58 -04:00
Dave Cameron
b83de7eb11 Use drf_yasg to generate swagger
drf_yasg provides more complete swagger output, allowing for generation
of usable clients.

Some custom work was needed to accommodate Netbox's custom field
serializers, and to provide x-nullable attributes where appropriate.
2018-03-15 17:07:58 -04:00
Jeremy Stretch
38a208242b Closes #1945: Implemented a VLAN members view 2018-03-15 15:33:13 -04:00
Jeremy Stretch
4acd8e180d Merge pull request #1902 from lae/feature/ansible-alt-install
Add Ansible alternative installation to README
2018-03-14 15:26:33 -04:00
Jeremy Stretch
debc8521a5 Closes #1968: Link device type instance count to filtered device list 2018-03-14 15:18:24 -04:00
Jeremy Stretch
8bd268d81c Closes #1944: Enable assigning VLANs to virtual machine interfaces 2018-03-14 14:53:28 -04:00
Jeremy Stretch
ae6848b194 Fixed Slack URL 2018-03-14 10:30:55 -04:00
Jeremy Stretch
b22744b031 Removed validation constraint prohibitting a VLAN from being both tagged and untagged 2018-03-09 14:00:48 -05:00
Jeremy Stretch
a75d7079df Fixed tests 2018-03-08 13:36:14 -05:00
Jeremy Stretch
aa8442a345 Removed VLAN assignments from interface bulk editing 2018-03-08 13:29:08 -05:00
Jeremy Stretch
70625a5cb0 Improved validation and workflow 2018-03-08 13:25:51 -05:00
Jeremy Stretch
7c043d9b4f Replaced tagged/untagged VLAN assignment widgets with a VLAN table; separate view for adding VLANs 2018-03-07 17:01:51 -05:00
Jeremy Stretch
546f17ab50 Closes #1866: Introduced AnnotatedMultipleChoiceField for filter forms 2018-03-07 14:16:38 -05:00
Jeremy Stretch
1c9986efc4 Closes #1949: Added a button to view elevations on rack groups list 2018-03-07 11:37:05 -05:00
Jeremy Stretch
8ae13e29f5 Fixes #1955: Require a plaintext value when creating a new secret 2018-03-07 11:20:10 -05:00
Jeremy Stretch
f5bb072f28 Fixes #1953: Ignore duplicate IPs when calculating prefix utilization 2018-03-07 11:08:28 -05:00
Jeremy Stretch
37eef0ba6d Fixes #1951: Fix TypeError exception when importing platforms 2018-03-06 12:10:02 -05:00
Jeremy Stretch
603b80db1b Fixes #1948: Fix TypeError when attempting to add a member to an existing virtual chassis 2018-03-06 11:48:26 -05:00
Jeremy Stretch
c823660a8f Post-release version bump 2018-03-01 15:36:32 -05:00
Jeremy Stretch
ec4d28ac6c Merge pull request #1937 from digitalocean/develop
Release v2.3.1
2018-03-01 15:36:10 -05:00
Jeremy Stretch
0c5ad85b35 Release v2.3.1 2018-03-01 15:30:09 -05:00
Jeremy Stretch
bdecf7a3e3 Fixes #1936: Trigger validation error when attempting to create a virtual chassis without specifying member positions 2018-03-01 14:40:39 -05:00
Jeremy Stretch
6b62720daf Closes #1910: Added filters for cluter group and cluster type 2018-03-01 13:22:43 -05:00
Jeremy Stretch
d48c450018 Merge pull request #1925 from lampwins/bug/1921
fixed #1921 - create interfaces with 802.1q in api
2018-03-01 13:17:16 -05:00
Jeremy Stretch
078404fb59 Fixes #1926: Prevent reassignment of parent device when bulk editing VC member interfaces 2018-03-01 13:10:36 -05:00
Jeremy Stretch
4bb526896f Fixes #1934: Fixed exception when rendering export template on an object type with custom fields assigned 2018-03-01 12:37:12 -05:00
Jeremy Stretch
0476006ef2 Merge pull request #1929 from lampwins/bug/1928
Fixed #1928 form bound check for site and vlan group
2018-03-01 12:22:17 -05:00
John Anderson
19831f0177 Merge branch 'develop' into bug/1921 2018-03-01 12:11:46 -05:00
Jeremy Stretch
fc9871fba3 Fixes #1935: Correct API validation of VLANs assigned to interfaces 2018-03-01 12:05:25 -05:00
John Anderson
b34f4f8e43 refactor to handle M2M validation in ValidatedModelSerializer 2018-03-01 11:31:56 -05:00
John Anderson
0357d8522c Merge branch 'develop' into bug/1921 2018-03-01 11:26:52 -05:00
Jeremy Stretch
08d06bd781 Fixes #1921: Ignore ManyToManyFields when validating a new object created via the API 2018-03-01 11:16:28 -05:00
Jeremy Stretch
01a97add2a Fixes #1927: Include all VC member interaces on A side when creating a new interface connection 2018-03-01 09:49:17 -05:00
John Anderson
3cb351dceb fixed form bound check for site and vlan group 2018-02-28 16:31:53 -05:00
Jeremy Stretch
9e11591b3b Post-release version bump (a bit late) 2018-02-27 17:56:18 -05:00
John Anderson
e4c1cece75 fixed #1921 - create interfaces with 801.1q in api 2018-02-27 16:19:28 -05:00
Jeremy Stretch
6881a98048 Fixes #1924: Include VID in VLAN lists when editing an interface 2018-02-27 16:10:02 -05:00
Jeremy Stretch
36de9f10d6 Closes #1918: Add note about copying media directory to upgrade doc 2018-02-27 15:54:25 -05:00
Jeremy Stretch
1cc135f01f Fixes #1919: Prevent exception when attempting to create a virtual machine without selecting devices 2018-02-27 15:40:24 -05:00
Jeremy Stretch
079c8894fa Fixes #1915: Redirect to device view after deleting a component 2018-02-27 14:59:45 -05:00
Jeremy Stretch
957074a134 Merge pull request #1913 from digitalocean/develop
Release v2.3.0
2018-02-26 14:23:03 -05:00
Jeremy Stretch
970759ed8b Release v2.3.0 2018-02-26 14:19:38 -05:00
Jeremy Stretch
22f17a1424 Merge branch 'develop-2.3' into develop 2018-02-26 14:14:47 -05:00
Jeremy Stretch
5ed797cfc9 Fixes #1907: Allow removing an IP as the primary for a device when editing the IP directly 2018-02-26 14:13:34 -05:00
Jeremy Stretch
8ad59058a5 Updated requirements list 2018-02-26 13:54:19 -05:00
Jeremy Stretch
ec7bbcf90d Closes #1899: Prefer binary package of psycopg2 2018-02-26 13:40:04 -05:00
Jeremy Stretch
37dde72c8f Corrected order of arguments on DeviceVCMembershipForm 2018-02-26 13:28:05 -05:00
Jeremy Stretch
972f9be291 Formatting correction 2018-02-26 13:20:28 -05:00
Jeremy Stretch
8b33b888b2 Merge branch 'develop' into develop-2.3 2018-02-21 16:16:20 -05:00
Jeremy Stretch
d29fd338eb Post-release version bump 2018-02-21 16:13:29 -05:00
Jeremy Stretch
c4f7e8121a Merge pull request #1903 from digitalocean/develop
Release v2.2.10
2018-02-21 16:05:45 -05:00
Jeremy Stretch
8b5dba25f5 Release v2.2.10 2018-02-21 16:04:15 -05:00
Musee Ullah
e18b5f5fd4 Add Ansible alternative installation to README 2018-02-22 05:56:33 +09:00
Jeremy Stretch
a5dc9537e5 Closes #1693: Allow specifying loose or exact matching for custom field filters 2018-02-21 15:40:11 -05:00
Jeremy Stretch
3064948d8c Closes #1801: Update list of rack groups when selecting a site to filter by in rack elevations list 2018-02-21 14:06:38 -05:00
John Eismeier
e6bcc4a3fe Propose fix typos (#1897) 2018-02-21 12:39:29 -05:00
Jeremy Stretch
6967b6bdc5 Fixes #1892: Removed convenience function from an old migration (see #632) to fix database error on extras/0009_topologymap_type 2018-02-21 12:00:38 -05:00
Jeremy Stretch
a8977a5dec Closes #1885: Added a device filter field for primary IP 2018-02-21 10:55:49 -05:00
Jeremy Stretch
b837e8ea0b Fixes #1886: Allow setting the primary IPv4/v6 address for a VirtualMachine via the web UI 2018-02-21 10:49:40 -05:00
Jeremy Stretch
110052fa0f Fixes #1889: Consistent ordering of interface fields on add/edit 2018-02-21 10:38:45 -05:00
Jeremy Stretch
84bb977d2e Finished VirtualChassis list view 2018-02-21 09:53:23 -05:00
Jeremy Stretch
2d93c2b2da Closes #78: Implemented ability to render topology maps for console/power 2018-02-15 12:10:29 -05:00
Jeremy Stretch
9e4f2a9614 Fixed panel heading CSS class 2018-02-15 10:01:02 -05:00
Jeremy Stretch
5412a9f8ea Exclude devices already assigned to a VC from the list of potential VC members 2018-02-14 13:36:05 -05:00
Jeremy Stretch
d7177d3e05 Fixed typo in template 2018-02-14 13:35:25 -05:00
Jeremy Stretch
a21bd81681 VirtualChassis form validation cleanup 2018-02-14 12:47:10 -05:00
Jeremy Stretch
e653f35bf1 Fixes #1884: Provide additional context to identify devices when creating/editing avirtual chassis 2018-02-14 11:14:04 -05:00
John Anderson
28ea06a8bc Fix for bulk interface edit form 802.1Q settings (#1882)
* fixes #1881 - bulk interface 802.1Q settings form

* fix PEP8 newline

* PEP8 fixup
2018-02-14 10:42:12 -05:00
Jeremy Stretch
86b0491b68 Closes #1876: Added explanatory title text to disabled NAPALM buttons on device view 2018-02-13 11:03:31 -05:00
Jeremy Stretch
c8309581be Fixes #1869: Corrected ordering of VRFs with duplicate names 2018-02-07 13:40:08 -05:00
Jeremy Stretch
376c531fe4 Template libraries cleanup 2018-02-07 13:35:19 -05:00
Jeremy Stretch
b2c5bcd4f1 Upgraded jquery to v3.3.1 2018-02-06 15:11:29 -05:00
Jeremy Stretch
73c64272d8 Merge branch 'develop' into develop-2.3 2018-02-06 14:58:11 -05:00
Jeremy Stretch
11fe54753e Fixes #1867: Allow filtering on device status with multiple values 2018-02-06 14:10:42 -05:00
Jeremy Stretch
69f921aea9 Closes #1864: Added a 'status' field to the circuit model 2018-02-06 14:06:05 -05:00
Jeremy Stretch
594ef71027 Fixes #1860: Do not populate initial values for custom fields when editing objects in bulk 2018-02-02 21:30:16 -05:00
Jeremy Stretch
d25d8c21f6 Eliminated queries for distinct related object counts for better performance 2018-02-02 17:46:23 -05:00
Jeremy Stretch
835d13542f Fixes #1858: Include device/CM count for cluster list in global search results 2018-02-02 17:11:46 -05:00
Jeremy Stretch
7f5a3fffd3 Fixed related object links for platform/role tables 2018-02-02 16:49:38 -05:00
Jeremy Stretch
1890e710cb Fixed quoting of line breaks inside a CSV field 2018-02-02 16:31:23 -05:00
Jeremy Stretch
a9fefbec5c Added missing CSV header 2018-02-02 16:23:07 -05:00
Jeremy Stretch
b96e3af6c7 Closes #1714: Standardized CSV export functionality for all object lists 2018-02-02 16:12:57 -05:00
Jeremy Stretch
12e6fe1d50 Standardized declaration of csv_headers on models 2018-02-02 14:26:16 -05:00
Jeremy Stretch
60c03a646c Fixes #1859: Implemented support for line breaks within CSV fields 2018-02-02 13:32:16 -05:00
Jeremy Stretch
59dcbce417 Refactored CSV export logic 2018-02-02 11:36:45 -05:00
Jeremy Stretch
df10fa87d3 Replaced IRC with Slack; formatting cleanup 2018-02-01 16:52:24 -05:00
Jeremy Stretch
a954406d1f Changed IRC to Slack; added warning about noisy comments 2018-02-01 16:39:48 -05:00
Jeremy Stretch
e2213f458f Allow assignment of services to IPs on any VC member 2018-02-01 16:11:04 -05:00
Jeremy Stretch
55adcc1f0c Additional validation cleanup 2018-02-01 15:53:59 -05:00
Jeremy Stretch
d6eaa3d0cc Added virtual chassis tests 2018-02-01 13:52:41 -05:00
Jeremy Stretch
25ad58d42c Cleaned up API for virtual chassis 2018-02-01 13:02:34 -05:00
Jeremy Stretch
b61bccbb67 Added virtual chassis member remove view 2018-02-01 12:49:23 -05:00
Jeremy Stretch
f1da517c84 Added virtual chassis member add view 2018-02-01 11:39:13 -05:00
Jeremy Stretch
a4019be28c Collapsed VCMembership into the Device model (WIP) 2018-01-31 22:47:27 -05:00
Jeremy Stretch
36090d9f02 Post-release version bump 2018-01-31 11:15:26 -05:00
Jeremy Stretch
6b101d2c49 Merge branch 'develop' into develop-2.3 2018-01-31 11:13:17 -05:00
Jeremy Stretch
6436d703f5 Merge pull request #1852 from digitalocean/develop
Release v2.2.9
2018-01-31 10:43:20 -05:00
Jeremy Stretch
b3243704df Release v.2.2.9 2018-01-31 10:30:55 -05:00
Jeremy Stretch
8bedfcfc64 Added warning message about automatically deleting child inventory items 2018-01-31 10:25:06 -05:00
Jeremy Stretch
e0aa2c33e9 Fixes #1850: Fix TypeError when attempting IP address import if only unnamed devices exist 2018-01-31 10:03:05 -05:00
Jeremy Stretch
49f268a14c Added report results to the home page 2018-01-30 21:01:08 -05:00
Jeremy Stretch
2bb0e65aea Closes #144: Implemented list and bulk edit/delete views for InventoryItems 2018-01-30 17:46:00 -05:00
Jeremy Stretch
8b6d731cb6 Fixes #1838: Fix KeyError when attempting to create a VirtualChassis with no devicesselected 2018-01-30 16:42:52 -05:00
Jeremy Stretch
1cd629efb3 #1843: Allow assignment of VC member interfaces to VC master LAG 2018-01-30 16:34:42 -05:00
Jeremy Stretch
2f7f5425d8 Fixes #1848: Allow null value for interface encapsulation mode 2018-01-30 16:20:50 -05:00
Jeremy Stretch
215156c333 Fixes #1847: Fix RecursionError when VC master device is unnamed 2018-01-30 16:08:43 -05:00
Jeremy Stretch
a5d2055c11 Closes #1073: Include prefixes/IPs from all VRFs when viewing the children of a container prefix in the global table 2018-01-30 13:39:33 -05:00
Jeremy Stretch
ffc2c564b8 Cleaned up InventoryItem add/edit/delete links and return URL 2018-01-30 13:07:10 -05:00
Jeremy Stretch
16f222b0ab Closes #1366: Enable searching for regions by name/slug 2018-01-30 12:11:20 -05:00
Jeremy Stretch
3edf90714a Closes #1406: Display tenant description as title text in object tables 2018-01-30 11:57:21 -05:00
Jeremy Stretch
4e8fc03c2b Fixes #1845: Correct display of VMs in list with no role assigned 2018-01-30 11:18:37 -05:00
Jeremy Stretch
5037283b62 Removed support for NAPALM 1.x 2018-01-25 14:35:19 -05:00
Jeremy Stretch
f2c9135b96 Merge branch 'develop' into develop-2.3 2018-01-25 14:21:25 -05:00
Jeremy Stretch
7378d82bc4 Closes #1821: Added 'description' field to Site model 2018-01-25 13:29:09 -05:00
Jeremy Stretch
ed10a99771 Closes #1758: Added 'status' field to Site model 2018-01-25 13:07:04 -05:00
Jeremy Stretch
4df128d34e Renamed device status constants for clarity 2018-01-25 12:20:24 -05:00
Jeremy Stretch
33d0db5854 Highlight renamed components and allow for multiple previews 2018-01-25 11:59:43 -05:00
Jeremy Stretch
4a57a554da Allow interface mode to be null (for routed interfaces) 2018-01-25 11:45:12 -05:00
Jeremy Stretch
21fe7c57d8 Closes #1835: Consistent position of previous/next rack buttons 2018-01-25 10:19:45 -05:00
Jeremy Stretch
3bcc1429dd Merge pull request #1833 from lampwins/api-docs
added statement and example for using ForeignKey ID's in write actions in api docs
2018-01-22 16:57:08 -05:00
Jeremy Stretch
9a6a479452 Added VirtualChassis to the docs 2018-01-22 16:54:39 -05:00
Jeremy Stretch
c43487b741 Extend IP address device filter to match virtual chassis members 2018-01-22 16:42:19 -05:00
John Anderson
6b50755a5a fixed duplicate api docs example and grammar 2018-01-22 16:26:51 -05:00
Jeremy Stretch
53998e0fff Closes #1828: Added warning about media directory permissions 2018-01-22 16:04:19 -05:00
John Anderson
7341ae087c added statement and exaple for using ForeignKey ID's in write actions 2018-01-22 10:43:19 -05:00
Jeremy Stretch
9ea8dca4e3 Evaluate device_id rather than pulling entire device (DB optimization) 2018-01-19 16:16:45 -05:00
Jeremy Stretch
d80e64b2cc Reflect virtual chassis membership in display_name 2018-01-19 15:53:49 -05:00
Jeremy Stretch
d7354f4dab Completed virtual chassis API serializers 2018-01-19 13:34:37 -05:00
Jeremy Stretch
8d1676db54 Implemented a view for adding individual devices to an existing virtual chassis 2018-01-19 12:34:09 -05:00
Jeremy Stretch
0714a40509 Merge branch 'develop' into develop-2.3 2018-01-19 10:54:26 -05:00
Jeremy Stretch
5262156e1a Fixes #1818: InventoryItem API serializer no longer requires specifying a null value for items with no parent 2018-01-19 10:30:26 -05:00
Jeremy Stretch
7ac27b59c6 Closes #1824: Add virtual machine count to platforms list 2018-01-19 09:25:16 -05:00
Jeremy Stretch
fc7a43f23e Closes #1781: Enable bulk renaming of device components 2018-01-10 15:48:07 -05:00
Jeremy Stretch
d5ecfe7bef Fixes #1809: Populate tenant assignment from parent when creating a new prefix 2018-01-10 09:38:55 -05:00
Jeremy Stretch
e58d1ac87e Fixes #1807: Populate VRF from parent when creating a new prefix 2018-01-05 15:31:48 -05:00
Jeremy Stretch
bb653e733c Fixes #1621: Tweaked LLDP interface name evaluation logic 2018-01-05 15:19:27 -05:00
Jeremy Stretch
9c27d18d6c Fix bulk creation of Secrets via API 2018-01-02 17:07:21 -05:00
Jeremy Stretch
e5c13d2d72 Fix bulk creation of VCMemberships via API 2018-01-02 16:40:52 -05:00
Jeremy Stretch
b27529d927 Added bulk creation API tests 2018-01-02 16:29:44 -05:00
Jeremy Stretch
95257114df Merge pull request #1803 from bonki/doc-typo-ldap
Fixes #1802: Typo in ldap.md
2018-01-02 11:21:32 -05:00
Adrian Frühwirth
935da0d51f Fixes #1802: Typo in ldap.md 2017-12-29 13:29:07 +01:00
Jeremy Stretch
78ed85943b Fixes #1765: Improved rendering of null options for model choice fields in filter forms 2017-12-26 12:08:22 -05:00
Jeremy Stretch
7d87cc498a Converted remaining legacy views to class-based views for the DCIM app 2017-12-21 15:08:05 -05:00
Jeremy Stretch
a523d25c0d More table cleanup 2017-12-21 13:33:41 -05:00
Jeremy Stretch
d84e5d1839 Cleaned up component tables and checkbox toggling 2017-12-21 13:29:02 -05:00
Jeremy Stretch
063e79451f Closes #1321: Added created and last_updated fields for relevant models to their API serializers 2017-12-21 10:49:40 -05:00
Jeremy Stretch
b4a842d9da Post-release version bump 2017-12-20 15:32:57 -05:00
Jeremy Stretch
ec0cb7a8bc Merge pull request #1789 from digitalocean/develop
Release v2.2.8
2017-12-20 15:27:22 -05:00
Jeremy Stretch
841471104b Release v2.2.8 2017-12-20 15:24:07 -05:00
Jeremy Stretch
ac71416eb9 Closes #1775: Added instructions for enabling STARTTLS for LDAP authentication 2017-12-20 14:48:42 -05:00
Jeremy Stretch
779d685335 Closes #1784: Added cluster_type filters for virtual machines 2017-12-20 14:24:12 -05:00
Jeremy Stretch
4d1e798c56 Merge pull request #1780 from explody/fix_1778
Fix for #1778.
2017-12-20 14:17:45 -05:00
Jeremy Stretch
a598035236 Closes #1774: Include a button to refine search results for all object types under global search 2017-12-20 14:09:52 -05:00
Jeremy Stretch
50395aa821 Closes #1773: Moved child prefixes table to its own view 2017-12-20 14:01:37 -05:00
Jeremy Stretch
6d9c8fd85b Fixes #1787: Added missing site field to virtualization cluster CSV export 2017-12-20 13:18:30 -05:00
Jeremy Stretch
b65d994397 Fixes #1136: Enforce model validation during bulk update 2017-12-20 13:04:00 -05:00
Jeremy Stretch
b20258c66e Closes #1283: Added a time zone field to the site model 2017-12-19 17:24:14 -05:00
Jeremy Stretch
9984238f2a Closes #1744: Allow associating a platform with a specific manufacturer 2017-12-19 16:15:26 -05:00
Jeremy Stretch
c3599bacf2 Fixes #1785: Omit filter forms from browsable API 2017-12-19 15:30:55 -05:00
Jeremy Stretch
c10481b99d Fixes #1783: Added vm_role filter for device roles 2017-12-19 09:37:26 -05:00
Jeremy Stretch
02e01b7386 Merge pull request #1782 from digitalocean/99-virtual-chassis
Virtual Chassis Support
2017-12-18 17:09:53 -05:00
Jeremy Stretch
ca7147a0a7 PEP8 fixes 2017-12-18 16:52:49 -05:00
Jeremy Stretch
022c360964 Ignore VC member interfaces where mgmt_only=True 2017-12-18 16:44:44 -05:00
Jeremy Stretch
d41f4d2db3 Return all VC member interfaces when filtering for the master device; remove virtual_chassis_id filter 2017-12-18 16:22:49 -05:00
Jeremy Stretch
4871682dc6 Allow designating primary IPs assigned to a device's peer VC members 2017-12-18 16:08:46 -05:00
Mike Culbertson
1cebc1248b Fix for #1778.
This will set initial values for visible bulk-add form fields from query args.
2017-12-16 12:28:37 -05:00
Jeremy Stretch
70d235f99e Added virtual chassis tests 2017-12-15 17:21:43 -05:00
Jeremy Stretch
153409d37e Obsoleted ComponentEditView and ComponentDeleteView 2017-12-15 15:57:49 -05:00
Jeremy Stretch
67a30fdf91 Added virtual_chassis_id API filter for interfaces 2017-12-15 15:31:35 -05:00
Jeremy Stretch
911ce3f047 Display member interfaces when viewing VC master device 2017-12-15 15:24:03 -05:00
Jeremy Stretch
c97f7041a7 Closes #1772: Added position filter for devices 2017-12-14 13:12:04 -05:00
Jeremy Stretch
89bfb4f722 Closes #1771: Added name filter for racks 2017-12-14 13:05:26 -05:00
Jeremy Stretch
da3935ff36 Fixes #1766: Fixed display of "select all" button on device power outlets list 2017-12-13 15:23:35 -05:00
Jeremy Stretch
06810bff91 Fixes #1764: Fixed typos in export buttons 2017-12-13 11:55:31 -05:00
Jeremy Stretch
a9af75bbd1 Fixes #1767: Use proper template for 404 responses 2017-12-13 11:49:36 -05:00
Jeremy Stretch
da2bff691b Added views for editing/deleting VCMemberships 2017-12-08 12:51:52 -05:00
Jeremy Stretch
a85b3aa69f Added a form to edit virtual chassis 2017-12-07 17:05:03 -05:00
Jeremy Stretch
859f89101e Fixes #1727: Added missing import for M2M_FIELD_TYPES 2017-12-07 15:36:08 -05:00
Jeremy Stretch
2545912532 Merge branch 'develop-2.3' into 99-virtual-chassis 2017-12-07 15:30:58 -05:00
Jeremy Stretch
2fc1519bc6 Merge branch 'develop' into develop-2.3 2017-12-07 14:56:16 -05:00
Jeremy Stretch
be6ef15ffa Post-release version bump 2017-12-07 14:54:16 -05:00
Jeremy Stretch
e98f0c39d1 Merge pull request #1757 from digitalocean/develop
Release v2.2.7
2017-12-07 14:52:28 -05:00
Jeremy Stretch
5666079d92 Release v2.2.7 2017-12-07 14:50:44 -05:00
Jeremy Stretch
85f5ba9a25 Fixes #1756: Improved natural ordering of console server ports and power outlets 2017-12-07 13:22:48 -05:00
Jeremy Stretch
df141a48d9 Fixed typo 2017-12-06 12:17:04 -05:00
Jeremy Stretch
fed6fc131b Fixes #1751: Corrected filtering for IPv6 addresses containing letters 2017-12-05 16:10:45 -05:00
Jeremy Stretch
cf49891853 Fixes #1740: Delete session_key cookie on logout 2017-12-05 14:19:24 -05:00
Jeremy Stretch
de2a894269 Closes #1737: Added a 'contains' API filter to find all prefixes containing a given IP or prefix 2017-11-30 12:37:41 -05:00
Jeremy Stretch
34d10f8db7 Fixes #1741: Fixed Unicode support for secret plaintexts 2017-11-29 15:16:11 -05:00
Jeremy Stretch
68f76465cf Fixes #1743: Include number of instances for device types in global search 2017-11-29 14:07:41 -05:00
Jeremy Stretch
5f91413023 Added initial UI views for virtual chassis assignment 2017-11-29 12:58:36 -05:00
Jeremy Stretch
45d6955260 Fixed search field length in search view 2017-11-28 09:27:31 -05:00
Jeremy Stretch
3b801d43bc Moved VC master designation to membership model 2017-11-27 15:59:13 -05:00
Jeremy Stretch
30df060357 Closes #1722: Added VM count to site view 2017-11-27 10:59:24 -05:00
Jeremy Stretch
252be84bf0 Corrected tenant inheritance for new IP addresses created from a parent prefix 2017-11-22 13:00:48 -05:00
Jeremy Stretch
40ab272995 Fixes #1721: Differentiated child IP count from utilization percentage for prefixes 2017-11-22 12:40:58 -05:00
Jeremy Stretch
0ec3b5db8b Closes #1722: Added virtual machine count to sites list 2017-11-22 12:19:04 -05:00
Jeremy Stretch
55e07c1c9a Initial work on virtual chassis support 2017-11-17 16:47:26 -05:00
Jeremy Stretch
7e475511b6 Fixed version number 2017-11-17 12:06:52 -05:00
Jeremy Stretch
ca77e4545a Merge branch 'develop' into develop-2.3 2017-11-17 12:05:38 -05:00
Jeremy Stretch
5dc9723585 Post-release version bump 2017-11-16 12:01:09 -05:00
Jeremy Stretch
50a451eddc Merge pull request #1720 from digitalocean/develop
Release v2.2.6
2017-11-16 12:00:34 -05:00
Jeremy Stretch
3f8350b78f Release v2.2.6 2017-11-16 11:57:43 -05:00
Jeremy Stretch
500a56b869 Fixes #1718: Set empty label to 'Global' or VRF field in IP assignment form 2017-11-16 11:54:23 -05:00
Jeremy Stretch
e50b7174bf Closes #1669: Clicking "add an IP" from the prefix view will default to the first available IP within the prefix 2017-11-15 15:26:00 -05:00
Jeremy Stretch
8299c735b1 Fixes #1599: Reduce mobile cut-off for navigation menu to 960px 2017-11-15 14:57:56 -05:00
Jeremy Stretch
124878ed22 Fixes #1599: Display global search in navigation menu unless display is less than 1200px wide 2017-11-15 14:44:33 -05:00
Jeremy Stretch
1c09570805 Added nested representations of user and tenant to the rack reservation serializer 2017-11-15 14:15:44 -05:00
Jeremy Stretch
e56797737d A bit of cosmetic cleanup from #1672 2017-11-15 14:06:58 -05:00
Jeremy Stretch
81852de1fa Resolved migration collision from #1672 2017-11-15 13:57:19 -05:00
Nicholas Totsch
fbd39da8ca Add Tenancy to Rack Reservations; Fixes #1592 (#1672)
* fixed prefix header to represent new serial "vlan_vid"

* shows option in creation now

* fixed visibility on rack page

* cleanup

* Added view to Tenant page

* Moved migration for update from #1666 and fixed tenant enumeration in FilterForm

* Fixed conflict #1

* Fixed filters from merge and made migration merge

* added tenant to api

* Fixed migrations problem

* Added Tenant to bulkedit option
2017-11-15 13:54:49 -05:00
Jeremy Stretch
db0ef95fe3 Cleaned up bulk IP provisioning a bit 2017-11-15 13:52:14 -05:00
Jeremy Stretch
d888aa67f9 Fixes #1715: Added missing import buttons on object lists 2017-11-15 12:52:21 -05:00
Jeremy Stretch
0cb3e1749b Fixes #1717: Fixed inteface validation for virtual machines 2017-11-15 12:37:08 -05:00
Jeremy Stretch
b5a51aced3 Fixes #1645: Simplified interface serialzier for IP addresses and optimized API view queryset 2017-11-15 12:21:52 -05:00
Jeremy Stretch
04ba57cb38 Fixed up validation of Interface VLAN assignments 2017-11-14 16:15:23 -05:00
Jeremy Stretch
ba42ad2115 Merge branch '150-interface-vlans' into develop-2.3 2017-11-14 15:36:14 -05:00
Jeremy Stretch
5c13382071 Closes #1706: Added deprecation warning for Python 2 2017-11-14 15:07:13 -05:00
Jeremy Stretch
3df8c63d5c Merge branch 'develop' into develop-2.3 2017-11-14 14:38:32 -05:00
Jeremy Stretch
8ff10d5995 Post-release version bump 2017-11-14 13:29:46 -05:00
Jeremy Stretch
a5a7358d26 Merge pull request #1708 from digitalocean/develop
Release v2.2.5
2017-11-14 13:25:11 -05:00
Jeremy Stretch
63ac8863f3 Release v2.2.5 2017-11-14 13:20:15 -05:00
Jeremy Stretch
2047a16a57 Fixes #1703: Added API serializer validation for custom integer fields 2017-11-14 13:15:09 -05:00
Jeremy Stretch
8d6d55d628 Fixes #1705: Fixed filtering of devices with a status of offline 2017-11-14 12:58:47 -05:00
Karl
9a7dd5ea19 Update 0008_reports.py (#1702)
* Update 0008_reports.py

PG10 version string appears to, at least on Windows, contain a comma.

* Fix missing re import.

Fix missing re import.

* Update 0008_reports.py
2017-11-13 15:11:41 -05:00
Jeremy Stretch
30b544a743 Fixes #1642: Validate device type classification when creating console server ports and power outlets 2017-11-10 15:01:46 -05:00
Jeremy Stretch
a0bb7b08bd Closes #1512: Added a view to search for an IP address being assigned to an interface 2017-11-10 11:58:59 -05:00
Jeremy Stretch
e1d655cb23 Fixes #1471: Correct bulk selection of IP addresses within a prefix assigned to a VRF 2017-11-10 09:34:30 -05:00
Jeremy Stretch
5d46a112f8 #1694: Initial work on "next available" prefix provisioning 2017-11-09 16:59:50 -05:00
Jeremy Stretch
a1b1e261de Fixes #1699: Correct nested representation in the API of primary IPs for virtual machines and add missing primary_ip property 2017-11-09 09:33:40 -05:00
Jeremy Stretch
e01e5e6b0e Standardize on JSON data format for all POST/PUT test client requests 2017-11-08 13:54:35 -05:00
Jeremy Stretch
4f2dc50b5c Extended prefix 'available-ips' endpoint to accept multiple objects (related to #1553) 2017-11-08 13:48:33 -05:00
Jeremy Stretch
c3e5106b04 Restored search method on prefix filter 2017-11-08 10:33:30 -05:00
Jeremy Stretch
593ae295e3 Removed prefix parent filter (see #1684) 2017-11-08 09:57:35 -05:00
Jeremy Stretch
9d50b78b69 Fixes #1696: Fix for NAPALM v2.0+ 2017-11-08 09:51:37 -05:00
Jeremy Stretch
198170ca48 Closes #1553: Introduced support for bulk object creation via the API 2017-11-07 15:36:10 -05:00
Jeremy Stretch
00986fd7bf Closes #1691: Cleaned up and reorganized import statements 2017-11-07 11:08:23 -05:00
Jeremy Stretch
2519ebff9d Tweaked exception-handling middleware to preserve tracebacks 2017-11-06 17:48:13 -05:00
Jeremy Stretch
c33775d71e #1689: Fix for Python 2 2017-11-06 17:44:19 -05:00
Jeremy Stretch
6b0721cc21 Fixed PermissionError handling for Python 2 2017-11-06 17:24:09 -05:00
Jeremy Stretch
d306e76420 Fixes #1689: Disregard IP address mask when filtering for child IPs of a prefix 2017-11-06 10:07:44 -05:00
Jeremy Stretch
73cd76932a Closes #1679: Added IP address roles to device/VM interface lists 2017-11-03 17:00:08 -04:00
Jeremy Stretch
5d19a9f50f Rearranged device/VM view and expanded component lists 2017-11-03 16:58:56 -04:00
Jeremy Stretch
368c30ef9d Removed unused imports 2017-11-03 14:36:28 -04:00
Jeremy Stretch
f77bf72de8 Closes #1683: Replaced default 500 handler with custom middleware to provide preliminary troubleshooting assistance 2017-11-03 13:24:31 -04:00
Jeremy Stretch
f2fbd92f78 Tweaked the issue template 2017-11-03 10:50:02 -04:00
Jeremy Stretch
480134302f Refreshed contributing docs 2017-11-03 10:10:08 -04:00
Jeremy Stretch
74cc8c022c Fixes #1650: Correct numeric ordering for interfaces with no alphabetic type 2017-11-02 13:58:30 -04:00
Jeremy Stretch
c6f3b00f0e Fixes #1676: Correct filtering of child prefixes upon bulk edit/delete from the parent prefix view 2017-11-02 13:21:19 -04:00
Jeremy Stretch
626fbd1d10 Closes #1684: Replaced prefix 'parent' filter with 'within' and 'within_include' 2017-11-02 13:15:25 -04:00
Ichabond
b8df05cf88 Fixes #1655. Removed explicit field references. (#1656)
* Fixes #1655

Further field name references were found in `consoleport.html`. These have now been removed, so we rely on proper a proper `__str__` implementation of both `ConsolePort` and `ConsoleServerPort`.

* Fixes #1655: Removed explicit field references

Cleaned up all (notable) .name references, and removed them so __str__ can do the lifting. Did not remove the references where it was explicitly referenced to .name (eg. in details). Extended the Secret model to also include the name in __str__, since that was weirdly absent.

* Adapted PR to comply with comments

Re-introduced certain references to make sure explicit references are still used where needed.
2017-11-02 11:51:27 -04:00
Ryan Breaker
57973f62c5 Fix bug with numbers >10 2017-10-31 22:03:57 -05:00
Jeremy Stretch
e56fc4b1ee Post-release version bump 2017-10-31 15:25:59 -04:00
Jeremy Stretch
f9452163c5 Merge pull request #1671 from digitalocean/develop
Release v2.2.4
2017-10-31 15:21:23 -04:00
Jeremy Stretch
76ebd2d34f Release v2.2.4 2017-10-31 15:17:35 -04:00
Jeremy Stretch
85c273c8ca Fixes #1670: Corrected filter names (regression from #1649) 2017-10-31 14:47:14 -04:00
Jeremy Stretch
b9cd834e95 Post-release version bump 2017-10-31 14:07:19 -04:00
Jeremy Stretch
3067c3f262 Merge pull request #1668 from digitalocean/develop
Release v2.2.3
2017-10-31 14:02:15 -04:00
Jeremy Stretch
cfa6bee081 Release v2.2.3 2017-10-31 13:58:27 -04:00
Jeremy Stretch
b46cc2c1a9 Closes #1666: Allow modifying the owner of a rack reservation 2017-10-31 13:52:35 -04:00
Jeremy Stretch
5e734fc5a6 Merge pull request #1664 from wanglf/develop
Fixed #1612 missing field 'serial' in function to_csv()
2017-10-31 13:14:52 -04:00
wanglf
d08bc7767e Fixed missing field 'serial' in function to_csv() 2017-10-31 22:05:30 +08:00
Jeremy Stretch
bbd0761887 Fixes #1653: Remove outdated description for DeviceType's is_network_device flag 2017-10-30 17:45:05 -04:00
Jeremy Stretch
4668149943 Fixes #1649: Correct fitlering on null values (e.g. ?tenant_id=0) for django-filters v1.1.0+ 2017-10-30 17:20:22 -04:00
Jeremy Stretch
a5b7c057eb Merge pull request #1604 from RyanBreaker/virt-initial_data
Added virtualization fixture for initial_data
2017-10-30 15:30:28 -04:00
Jeremy Stretch
0a04bb110a Fixes #1603: Hide selection checkboxes for tables with no available actions 2017-10-27 10:33:58 -04:00
Jeremy Stretch
9c0b414676 Fixed vertical heigh of checboxes in tables 2017-10-27 10:31:54 -04:00
Jeremy Stretch
5be4b0c4fd Closes #1631: Added a post_run method to the Report class 2017-10-27 10:02:27 -04:00
Jeremy Stretch
8e0eab20e2 Merge pull request #1652 from Ichabond/develop
Fixes #1651: Remove explicit reference to the `name` field
2017-10-26 21:15:14 -04:00
Tom Strickx
1b5aa67f5d Remove explicit reference to the name field
Remove the explicit usage of the `name` field, and let the __str__ method of the ConsoleServerPort class handle the display
2017-10-26 09:24:39 -07:00
Ryan Breaker
02c278f393 Add Digital Ocean as an initial cluster 2017-10-26 10:05:18 -05:00
Ryan Breaker
e57b8aa26f E226 fix 2017-10-24 20:43:02 -05:00
Ryan Breaker
3d023126ba Refactor pattern check 2017-10-24 20:22:15 -05:00
Ryan Breaker
53f58d4496 Update comment 2017-10-24 20:03:10 -05:00
Ryan Breaker
1a6ee237f6 Update help text for ExpandableNameField (again) 2017-10-24 19:59:37 -05:00
Ryan Breaker
33a99441a4 Update help text for ExpandableNameField 2017-10-24 19:55:50 -05:00
Ryan Breaker
3df7e283e3 Prevent mismatch of cases in ranges 2017-10-24 19:46:12 -05:00
Ryan Breaker
b295849f53 Prevent mismatch of types in ranges 2017-10-24 19:30:43 -05:00
Ryan Breaker
c107f35118 Merge letters and numbers into one function 2017-10-24 17:55:00 -05:00
Ryan Breaker
3d91153275 Add alphabetic variants to interface expansions 2017-10-24 00:09:38 -05:00
Jeremy Stretch
54472b3806 Fixes #1634: Cluster should not be a required field when importing child devices 2017-10-23 13:17:51 -04:00
Jeremy Stretch
14e5f89feb Fixes #1624: Add VM count to device roles table 2017-10-23 13:15:34 -04:00
Jeremy Stretch
a0b93bb4df Fixes #1513: Correct filtering of custom field choices 2017-10-20 16:39:13 -04:00
Jeremy Stretch
85347d9675 Closes #999: Display devices on which circuits are terminated in circuits list 2017-10-20 16:27:19 -04:00
Ryan Breaker
060f7a7191 Add Hyper-V and Azure to Virtualization initial_data 2017-10-19 20:18:09 -05:00
Jeremy Stretch
81ca6f7cba #1621: Allow for loose matching of short-form IOS interface names when validating LLDP neighbors 2017-10-19 17:12:28 -04:00
Jeremy Stretch
515645bb4d Fixes #1620: Loosen IP address search filter to match all IPs that start with the given string 2017-10-18 14:29:56 -04:00
Jeremy Stretch
6ae6209457 Fixes #1619: Correct text-based filtering of IP network and address fields 2017-10-18 13:01:49 -04:00
Jeremy Stretch
272325ff05 Fixes #1618: Allow bulk deletion of all virtual machines 2017-10-18 12:31:58 -04:00
Jeremy Stretch
a84b49b92d Merge pull request #1597 from RyanBreaker/validations
Additional data validation for Rack model
2017-10-18 11:22:26 -04:00
Jeremy Stretch
b63efdd80b Post-release version bump 2017-10-17 11:25:12 -04:00
Jeremy Stretch
7a64404299 Merge pull request #1614 from digitalocean/develop
Release v2.2.2
2017-10-17 11:24:02 -04:00
Jeremy Stretch
2afa6ed2cb Release v2.2.2 2017-10-17 11:21:58 -04:00
Jeremy Stretch
34f1a9ebfb Fixes #1579: Devices already assigned to a cluster cannot be added to a different cluster 2017-10-17 09:59:35 -04:00
Jeremy Stretch
6f2f8697ae Fixes #1609: Added missing virtual_machine field to IP address interface serializer 2017-10-17 09:23:53 -04:00
Jeremy Stretch
6ec9d1d6ce Merge pull request #1598 from candlerb/candlerb/1498
Avoid creating repeated graph nodes where device matches multiple regexps
2017-10-16 17:16:46 -04:00
Jeremy Stretch
047f22e110 Fixes #1605: Added clusters and virtual machines to object list for global search 2017-10-16 16:44:15 -04:00
Ryan Breaker
a91fcbb310 Added virtualization fixture for loaddata initial_data 2017-10-16 14:59:39 -05:00
root
5fc3eac0f6 Avoid creating repeated graph nodes where device matches multiple regexps
Fixes #1498
2017-10-16 10:13:39 +00:00
Ryan Breaker
937faaf149 Add test for validation of device within rack height. 2017-10-15 19:03:28 -05:00
Ryan Breaker
115e7d6e50 Add group and site matching validation to Rack.clean() and testing 2017-10-15 18:35:03 -05:00
Jeremy Stretch
34259d5d9d Removed deprecated xstr and expand_pattern functions 2017-10-13 14:29:55 -04:00
Jeremy Stretch
91b6ebb0c0 Closes #1580: Allow cluster assignment when bulk importing devices 2017-10-13 14:19:41 -04:00
Jeremy Stretch
60b4f1f89f Fixes #1585: Fixed slug-based filtering of virtual machines 2017-10-13 12:14:19 -04:00
Jeremy Stretch
023ff6834a Designated new Docker build repo; removed stale Heroku build repo 2017-10-13 11:50:06 -04:00
Jeremy Stretch
d00cab0b0a Merge branch 'develop' of github.com:digitalocean/netbox into develop 2017-10-13 10:54:24 -04:00
Jeremy Stretch
17493ff655 Closes #1587: Add primary IP column for virtual machines in global search results 2017-10-13 10:53:25 -04:00
Jeremy Stretch
6c27e6c4fe Fixes #1584: Colorized virtual machine role column 2017-10-13 10:45:34 -04:00
Jeremy Stretch
4cb0be4df3 Fixes #1582: Add virtual_machine attribute to IPAddress 2017-10-13 10:42:45 -04:00
Jeremy Stretch
40f555a3b6 Merge pull request #1545 from digitalocean/mdl-ldap-docs-formatting
Minor LDAP documentation formatting cleanup
2017-10-12 23:42:29 -04:00
Jeremy Stretch
881fdbe893 Post-release version bump 2017-10-12 16:39:51 -04:00
Jeremy Stretch
2bda399982 Merge pull request #1577 from digitalocean/develop
Release v2.2.1
2017-10-12 16:11:17 -04:00
Jeremy Stretch
75d840fa1a PEP8 fix 2017-10-12 16:07:13 -04:00
Jeremy Stretch
800bdd8fc5 Release v2.2.1 2017-10-12 16:04:01 -04:00
Jeremy Stretch
b04ade8060 Fixes #1576: Move PostgreSQL validation logic into the relevant migration 2017-10-12 16:02:15 -04:00
Jeremy Stretch
7f4d96f33e Post-release version bump 2017-10-12 14:01:52 -04:00
Jeremy Stretch
74731bc6ae Merge pull request #1575 from digitalocean/develop
Release v2.2.0
2017-10-12 14:01:28 -04:00
Jeremy Stretch
9d8daca54d Release v2.2.0 2017-10-12 13:54:04 -04:00
Jeremy Stretch
bb898b719f Added reports CLI documentation 2017-10-12 13:48:08 -04:00
Jeremy Stretch
3a0b57b50f Fixed typo 2017-10-12 13:47:44 -04:00
Ryan Breaker
38d2333165 Add meta tags for charset and convert some tabs to spaces for consistency (#1574)
* Add meta tag for charset.

* Add meta tag for charset in 500.html and convert some tabs to spaces for consistency.
2017-10-12 13:39:28 -04:00
Jeremy Stretch
a7f0b5adb3 Refreshed installation docs 2017-10-12 13:38:23 -04:00
Jeremy Stretch
109fff0fa6 Reorganized the docs 2017-10-12 10:59:39 -04:00
Jeremy Stretch
8c591e7248 Fixed up reports documentation 2017-10-11 17:05:01 -04:00
Jeremy Stretch
0dafa10e27 Merge branch 'develop-2.2' into develop
Conflicts:
	netbox/netbox/settings.py
2017-10-11 16:45:18 -04:00
Jeremy Stretch
60e3ff0bf0 Updated version number for v2.2 build 2017-10-11 16:42:44 -04:00
Jeremy Stretch
6052405eb7 Added info about API _choices endpoints 2017-10-11 16:41:16 -04:00
Jeremy Stretch
17a45109f4 Bypass PostgreSQL 9.4 check if database is inaccessible 2017-10-11 16:28:05 -04:00
Jeremy Stretch
392b45e6cb Added warning for PostgreSQL 9.4 requirement 2017-10-11 16:20:50 -04:00
Jeremy Stretch
f27e1ba885 Check that PostgreSQL is 9.4 or higher on initialization 2017-10-11 16:16:44 -04:00
Jeremy Stretch
990c9d6f7c Updated package requirements 2017-10-11 15:11:51 -04:00
Jeremy Stretch
b301b8f6f2 Branded the browsable API template 2017-10-11 14:47:43 -04:00
Jeremy Stretch
924cf9bfd4 Removed obsolete docker build file 2017-10-11 14:39:51 -04:00
Jeremy Stretch
7e6573d191 Added missing permission class to reports API viewset 2017-10-11 14:37:25 -04:00
Jeremy Stretch
fdae3a3f31 Introduced the REPORTS_ROOT config parameter; Python2 fixes 2017-10-11 14:03:35 -04:00
Jeremy Stretch
51194e20f2 Improved template for when no reports are found 2017-10-11 13:30:38 -04:00
Jeremy Stretch
043f2cb214 Tweaked PostgreSQL version number in example 2017-10-11 13:12:49 -04:00
Jeremy Stretch
651d462456 Post-release version bump 2017-10-11 13:04:02 -04:00
Jeremy Stretch
7cb287d6c6 Merge pull request #1572 from digitalocean/develop
Release v2.1.6
2017-10-11 13:02:32 -04:00
Jeremy Stretch
d16d89028f Release v2.1.6 2017-10-11 12:59:43 -04:00
Jeremy Stretch
baa277c592 Updated install docs to install pip using setuptools 2017-10-11 12:22:35 -04:00
Jeremy Stretch
007f930fed Merge branch 'develop' into develop-2.2
Conflicts:
	netbox/utilities/api.py
2017-10-11 10:47:21 -04:00
Jeremy Stretch
f253f164a3 Closes #1510: Added ability to search by name when adding devices to a cluster 2017-10-11 10:14:09 -04:00
Jeremy Stretch
abfe71bb04 #1556: Swapped label and value 2017-10-10 17:47:53 -04:00
Jeremy Stretch
0c645b12d1 Moved querysets to separate files; tweaked interface ordering logic (#1523) 2017-10-10 17:23:41 -04:00
Jeremy Stretch
f824d1eb3b Closes #1556: Added API endpoints listing static field choices for each app 2017-10-10 16:41:35 -04:00
Jeremy Stretch
afbbe1148f Moved core API classes out of utilities 2017-10-10 14:21:20 -04:00
Jeremy Stretch
8403e91fc9 Fixes #1567: Prompt user for session key when importing secrets 2017-10-10 13:22:40 -04:00
Jeremy Stretch
c300879995 Fixes #1527: Replace deprecated pycrypto library with pycryptodome 2017-10-09 17:40:15 -04:00
Jeremy Stretch
ea1d298eb3 Closes #494: Include asset tag in device info pop-up on rack elevation 2017-10-09 16:25:52 -04:00
Jeremy Stretch
d17b36519a Closes #1479: Added CARP IP address role 2017-10-09 16:03:45 -04:00
Jeremy Stretch
f4f0896470 Added bulk import view for secret roles 2017-10-09 15:56:17 -04:00
Jeremy Stretch
2c9b41db75 Added bulk import view for circuit types 2017-10-09 15:52:22 -04:00
Jeremy Stretch
a38cd449c5 Added bulk import views for cluster types and groups 2017-10-09 15:49:48 -04:00
Jeremy Stretch
977cad3830 Added bulk import views for RIRs, roles, and VLAN groups 2017-10-09 15:42:06 -04:00
Jeremy Stretch
987587b5f3 Added NAPALM driver to platform table; removed legacy RPC client 2017-10-09 15:30:48 -04:00
Jeremy Stretch
9351f686b1 Added bulk import views for rack roles, device roles, and platforms 2017-10-09 15:28:46 -04:00
Jeremy Stretch
42e557bd3f Added bulk import view for tenant groups 2017-10-09 15:09:40 -04:00
Jeremy Stretch
35c2c8e8de #1444: Added a serial number field to the rack model 2017-10-09 15:01:57 -04:00
Jeremy Stretch
960e1d49c2 Closes #1551: Added CPAK, CFP2, and CFP4 100GE interface form factors 2017-10-09 14:45:10 -04:00
Jeremy Stretch
100d979d3e Fixes #1554: Don't require form_factor when creating an interface assigned to a virtual machine 2017-10-09 14:27:31 -04:00
Jeremy Stretch
b734599f40 Closes #1557: Added filtering for virtual machine interfaces 2017-10-09 13:59:53 -04:00
Jeremy Stretch
b41b4fc584 Closes #1506: Extended rack facility ID field from 30 to 50 characters 2017-10-09 13:44:22 -04:00
Jeremy Stretch
b69bf58c63 Closes #1548: Automatically populate tenant assignment when adding an IP address from the prefix view 2017-10-09 13:40:28 -04:00
Jeremy Stretch
a73e598a6e #1561: Selected related primary IPs for devices under global search 2017-10-09 13:32:30 -04:00
huzichunjohn
af2de6776d Closes #1561: Add Primary IP for a device to Search Results (#1565) 2017-10-09 13:28:39 -04:00
Jeremy Stretch
98195c9e74 Fixes #1550: Corrected interface connections link in navigation menu 2017-10-06 15:39:39 -04:00
Jeremy Stretch
97188ad85b Fixes #1563: Made necessary updates for Django REST Framework v3.7.0 2017-10-06 15:26:56 -04:00
Jeremy Stretch
9c673d2a2e Updated the docs to call out PostgreSQL 9.4+ as a requirement 2017-09-29 14:36:55 -04:00
Jeremy Stretch
77182d8711 Redesigned the navigation menus 2017-09-29 14:27:42 -04:00
Matt Layher
a01c9ff379 Minor LDAP documentation formatting cleanup 2017-09-29 13:22:42 -04:00
Jeremy Stretch
32399b0cb5 Merge branch 'develop-2.2' of github.com:digitalocean/netbox into develop-2.2 2017-09-29 12:54:38 -04:00
Jeremy Stretch
fe3cdb1e20 Merge pull request #1359 from paravoid/develop-2.1
Add bulk (CSV) import/export for more object types
2017-09-29 12:35:36 -04:00
Jeremy Stretch
3d25cecc69 Merge branch 'develop-2.2' into develop-2.1 2017-09-29 12:30:36 -04:00
Jeremy Stretch
068c0ff36c Merge branch 'develop' into develop-2.1 2017-09-29 12:26:54 -04:00
Jeremy Stretch
86a298792a Corrected default return URL for BulkComponentCreateView 2017-09-29 12:15:14 -04:00
Jeremy Stretch
a6599874db #1493: Extended DeviceRole to include a toggle indicating applicability to virtual machines 2017-09-29 12:11:20 -04:00
Jeremy Stretch
0deae84ecb Renamed column 2017-09-29 11:50:26 -04:00
Jeremy Stretch
6243fbfd0d Closes #1493: Added functional roles for virtual machines 2017-09-29 11:13:41 -04:00
Jeremy Stretch
f49d7ce1da Removed deprecated NETBOX_USERNAME and NETBOX_PASSWORD configuration parameters 2017-09-29 10:18:18 -04:00
Jeremy Stretch
75fed52bb6 Fixed YAML 2017-09-28 17:55:05 -04:00
Jeremy Stretch
90ec177360 Troubleshooting Travis CI 2017-09-28 17:47:11 -04:00
Jeremy Stretch
667eadb430 Removed Docker bits from CI build 2017-09-28 17:36:05 -04:00
Jeremy Stretch
afbe0bc307 Merge pull request #1544 from digitalocean/reports
Closes #1511: Implemented reports
2017-09-28 17:16:10 -04:00
Jeremy Stretch
c65af6a74f Trying to get Travis to run PostgreSQL 9.4 2017-09-28 17:03:47 -04:00
Jeremy Stretch
669aee2d73 Bumped psycopg2 hoping to fix jsonb errors in CI 2017-09-28 16:43:35 -04:00
Jeremy Stretch
67f0dfa449 We need PostgreSQL 9.4 or higher for jsonb fields 2017-09-28 16:32:59 -04:00
Jeremy Stretch
e630a1ace1 Added docs for reports 2017-09-28 16:25:42 -04:00
Jeremy Stretch
21485ca6e2 Restrict the running of reports via API to POST requests 2017-09-28 16:04:37 -04:00
Jeremy Stretch
87fb4af5cb Cosmetic cleanup 2017-09-28 15:18:40 -04:00
Jeremy Stretch
e86ec75513 Fixed 'failure' field name 2017-09-28 14:57:54 -04:00
Jeremy Stretch
1ad099d9fd Added nav menu link to reports list 2017-09-28 14:54:11 -04:00
Jeremy Stretch
f9a677c1a3 Bugfixes 2017-09-28 13:36:50 -04:00
Jeremy Stretch
6c6b67330f Expanded report view 2017-09-28 13:35:18 -04:00
Jeremy Stretch
2b33e78fd3 Added a run view for reports 2017-09-28 12:51:10 -04:00
Jeremy Stretch
b65e9fe0f5 Fixed runreport management command 2017-09-28 12:50:52 -04:00
Jeremy Stretch
571b817f04 Moved ReportResult creation into Report.run() 2017-09-28 12:50:32 -04:00
Jeremy Stretch
e2b2815545 Fixes #1542: Added cluster field to device view 2017-09-28 10:21:41 -04:00
Jeremy Stretch
0ea58ab268 Fixes #1543: Added missing virtual machine field to IP address and service filters 2017-09-28 09:29:12 -04:00
Jeremy Stretch
2fbb39bf6f Started adding a view for individual reports 2017-09-27 17:39:22 -04:00
Jeremy Stretch
f4c87b3739 Removed custom permission 2017-09-26 17:31:16 -04:00
Jeremy Stretch
696d91daa3 Prettied up the reports list 2017-09-26 17:17:28 -04:00
Jeremy Stretch
9a1781e6e7 Added url field for nested report results 2017-09-26 16:55:25 -04:00
Jeremy Stretch
3395b51086 Cleaned up the API quite a bit 2017-09-26 16:36:43 -04:00
Jeremy Stretch
a3a77a0bb6 Fixes #1540: Added missing 'cluster' field to WritableDeviceSerializer 2017-09-26 15:45:14 -04:00
Jeremy Stretch
d35a2b0faa Extended reports API 2017-09-25 17:27:58 -04:00
Jeremy Stretch
88c57d002d Added navigation panel 2017-09-25 16:22:50 -04:00
Jeremy Stretch
0d9ff907a8 Merge branch 'develop' of github.com:digitalocean/netbox into develop-2.2 2017-09-25 15:12:47 -04:00
Jeremy Stretch
02d8897b82 Post-release version bump 2017-09-25 14:53:10 -04:00
Jeremy Stretch
aa8f734bd1 Merge pull request #1537 from digitalocean/develop
Release v2.1.5
2017-09-25 14:52:43 -04:00
Jeremy Stretch
0015484679 Release v2.1.5 2017-09-25 14:50:45 -04:00
Jeremy Stretch
aa5aa798e6 Fixes #1486: Ignore subinterface IDs when validating LLDP neighbor connections 2017-09-25 14:33:32 -04:00
Jeremy Stretch
63a4a70420 #1499: Added utilization graph to child prefixes for aggregates 2017-09-25 13:39:03 -04:00
Jeremy Stretch
2badb04a03 Fixes #1489: Corrected server error on validation of empty required custom field 2017-09-25 13:37:11 -04:00
Jeremy Stretch
a328e12642 Closes #1536: Improved formatting of aggregate prefix statistics 2017-09-25 12:14:55 -04:00
Jeremy Stretch
ce9f1eb201 Closes #1499: Added utilization graph to child prefixes table 2017-09-22 21:17:46 -04:00
Jeremy Stretch
b454ac9ce5 Merge pull request #1524 from larsuhartmann/develop
mentioned support of nested groups in LDAP installation documentation.
2017-09-22 16:19:32 -04:00
Jeremy Stretch
a010f7439d More work on #1523 (natural ordering for interfaces) 2017-09-22 16:18:43 -04:00
Jeremy Stretch
d7b0ba57e0 Merge pull request #1526 from tarkatronic/order_naturally_enhancements
Fixes #1523 - Interface.objects.order_naturally() enhancements
2017-09-22 14:12:43 -04:00
Jeremy Stretch
2ca161f3d8 Closes #1509: Extended cluster model to allow site assignment 2017-09-22 12:53:09 -04:00
Jeremy Stretch
79fdf641c0 Implemented rough UI for accessing report results 2017-09-22 12:11:10 -04:00
Joey Wilhelm
89194c067b Another test case to ensure subinterface ordering 2017-09-21 17:21:21 -07:00
Jeremy Stretch
b5ab498e75 Initial work on reports API 2017-09-21 16:32:05 -04:00
Joey Wilhelm
126a5e5e4e Fix order_naturally with unbalanced names and use RawSQL instead of extra 2017-09-21 11:00:55 -07:00
Jeremy Stretch
16d1f9aca8 Tweaked report run logic 2017-09-21 13:49:04 -04:00
Lars Hartmann
466ab0b404 mentioned support of nested groups in LDAP installation documentation. 2017-09-21 16:36:25 +02:00
Jeremy Stretch
a52a871a94 Fixes #1522: Removed object create/edit forms from the browsable API 2017-09-20 16:54:27 -04:00
Jeremy Stretch
4f9379a5e2 Cleaned up device interfaces/IPs table 2017-09-20 14:44:51 -04:00
Jeremy Stretch
4cfad2ef3b Added virtualization models to nbshell and fixed up status constants 2017-09-20 14:03:56 -04:00
Jeremy Stretch
6e81b0ac32 Merge pull request #1517 from huzichunjohn/issue_1514
Fixes #1514: No field to edit Cluster comments
2017-09-20 11:53:43 -04:00
Jeremy Stretch
d87023e1ce Limited virtual machine interfaces to type "virtual" (removed LAG type) 2017-09-20 11:47:37 -04:00
Jeremy Stretch
e31d2c9857 Fixes #1520: Redirect on GET request to bulk edit/delete views 2017-09-20 09:56:20 -04:00
johnhu
2174ccf016 Fixes #1514: No field to edit Cluster comments 2017-09-20 11:48:49 +00:00
Jeremy Stretch
8f1607e010 Initial work on reports 2017-09-19 17:47:42 -04:00
Jeremy Stretch
e93129f1ae Enabled export templates for clusters and virtual machines 2017-09-19 11:31:29 -04:00
Jeremy Stretch
35b4e190d9 Fixes #1508: Enabled creating custom fields for clusters and virtual machines via the admin UI 2017-09-19 10:40:38 -04:00
Ryan Breaker
b252f52f8e Added missing package for CentOS and Fedora (#1470)
Without the added package, installation fails during `pip install -r requirements.txt` on the pycrypto package for me in CentOS 6 and 7 and Fedora.
2017-09-18 16:37:51 -04:00
Chris Howells
eba30c4d79 Document the PATCH method. Closes #1481. (#1482) 2017-09-18 16:37:09 -04:00
Jeremy Stretch
ae1662e433 Fixes #1507: Fixed error when creating the next available IP from a prefix within a VRF 2017-09-18 16:34:28 -04:00
Jeremy Stretch
512a3bf09a Fixed #1500: Allow assigning IP addresses to virtual machines during CSV bulk import 2017-09-18 13:30:20 -04:00
Jeremy Stretch
9927ce14d3 Fixes #1502: Fixed CSV export for clusters and virtual machines 2017-09-18 13:12:58 -04:00
Jeremy Stretch
e5e169f476 Fixes #1504: Added missing vcpus/memory/disk fields to virtual machine API serializer 2017-09-18 12:54:50 -04:00
Jeremy Stretch
81df837a33 Closes #1485: Added LOGIN_BANNER configuration setting to display a banner on the login page 2017-09-15 15:28:09 -04:00
Jeremy Stretch
b84b526a43 Closes #1484: Added individual "add VLAN" buttons on the VLAN groups list 2017-09-15 15:14:35 -04:00
Jeremy Stretch
5fc92ab0e6 Removed old RPC-based LLDP neighbors code 2017-09-15 14:07:09 -04:00
Jeremy Stretch
039c2a6d57 Closes #1496: Added primary IP column to virtual machine list 2017-09-15 11:47:29 -04:00
Jeremy Stretch
d053422657 Fixes #1497: Added cluster_id filter 2017-09-15 11:36:06 -04:00
Jeremy Stretch
90d9eb3520 Closes #1490: Rearrange order of items in the virtualization nav menu 2017-09-15 11:08:23 -04:00
Jeremy Stretch
3466da4338 Merge branch 'develop' into develop-2.2
Conflicts:
	netbox/dcim/forms.py
2017-09-14 14:58:45 -04:00
Jeremy Stretch
17c1a1e465 Use add_blank_choice() to prepend a null choice to field options 2017-09-14 14:45:45 -04:00
Jeremy Stretch
6872ab0e14 Added a status field for virtual machines 2017-09-14 14:35:34 -04:00
Jeremy Stretch
136d16b7fd Don't add a placeholder option to multiselect widgets 2017-09-12 14:14:08 -04:00
Jeremy Stretch
d651deb31c Renamed form_class attribute to model_form for consistency 2017-09-12 13:55:11 -04:00
Jeremy Stretch
8fb37233f4 Started documenting utility views 2017-09-12 13:53:15 -04:00
Jeremy Stretch
700194b80d Implemented bulk interface creation for virtual machines 2017-09-12 12:49:01 -04:00
Jeremy Stretch
ef2dd673ec Finished bulk edit/delete views 2017-09-11 16:14:05 -04:00
Jeremy Stretch
7a3adca771 Added device count to clusters table 2017-09-11 15:51:03 -04:00
Jeremy Stretch
f66b0b7fea Finished implementing CSV imports 2017-09-11 15:42:18 -04:00
huzichunjohn
b6df0209ba Fixes #1472: Secret truncated when using '<' character (#1477) 2017-09-11 10:55:04 -04:00
Jeremy Stretch
6b53d263fe Fixes #1469: Allow a NAT IP to be assigned as the primary IP for a device 2017-09-01 13:00:44 -04:00
Jeremy Stretch
00024240bb Merge branch 'virtualization' into develop-2.2 2017-09-01 12:29:12 -04:00
Jeremy Stretch
237a889f54 Preserve query parameters on redirect after clicking "add another" 2017-09-01 12:28:34 -04:00
Jeremy Stretch
f5c265b7b5 Modified form action to preserve query parameters 2017-09-01 12:25:57 -04:00
Jeremy Stretch
1341ab5703 Fixed selection of sites not assigned to a region 2017-09-01 10:10:10 -04:00
Jeremy Stretch
3ded8196c4 Fixed IP toggle button 2017-09-01 10:09:52 -04:00
Jeremy Stretch
cbc239ceaa Added virtualization API tests 2017-08-31 14:03:12 -04:00
Jeremy Stretch
a6b43baafe Added documentation for virtualization data model 2017-08-31 13:22:12 -04:00
Jeremy Stretch
ba0a261840 Removed extraneous import 2017-08-31 13:04:56 -04:00
Jeremy Stretch
3bb0d523d3 Enabled services on virtual machines 2017-08-31 12:50:35 -04:00
Jeremy Stretch
85cb333a5d Added virtualization to API root 2017-08-31 11:36:29 -04:00
Jeremy Stretch
2abb073b3a Regenerated migrations 2017-08-31 10:17:59 -04:00
Jeremy Stretch
0634386b2e Merge branch 'develop' into virtualization 2017-08-31 10:14:50 -04:00
Jeremy Stretch
372e9335b1 Post-release version bump 2017-08-30 14:44:55 -04:00
Jeremy Stretch
f6d1163ddd Merge pull request #1461 from digitalocean/develop
Release v2.1.4
2017-08-30 14:43:01 -04:00
Jeremy Stretch
e6b6082a2b Release v2.1.4 2017-08-30 14:40:52 -04:00
Jeremy Stretch
e0ee0b9254 Closes #1460: Hostnames with no domain are now acceptable in custom URL fields 2017-08-30 14:08:39 -04:00
Jeremy Stretch
fde47133da Fix global search placeholder text 2017-08-30 12:07:11 -04:00
Jeremy Stretch
643f64df3f Closes #1341: Added a MEDIA_ROOT configuration setting to specify where uploaded files are stored on disk 2017-08-30 11:11:59 -04:00
Jeremy Stretch
a6ec1ba23b Closes #1376: Ignore anycast when detecting duplicate IPs 2017-08-30 10:46:30 -04:00
Jeremy Stretch
ae231b1d1b Moved circuit speed humanization to a template tag 2017-08-29 22:42:06 -04:00
Jeremy Stretch
ecdf66c454 Closes #1326: Added dropdown widget with common values for circuit speed fields 2017-08-29 22:29:30 -04:00
Jeremy Stretch
aaca28c1a6 Closes #1402: Increased max length of name field for device components 2017-08-29 17:27:30 -04:00
Jeremy Stretch
f0d8fdf2d8 Closes #1431: Added interface form factor for 10GBASE-CX4 2017-08-29 17:02:09 -04:00
Jeremy Stretch
c9288a052a Closes #1432: Added a commit_rate field to the circuits list search form 2017-08-29 16:48:11 -04:00
Jeremy Stretch
4b3a425888 Fixed role link on IP address view 2017-08-29 16:14:32 -04:00
Jeremy Stretch
71069ced7f Added platform info to VM view 2017-08-29 16:12:52 -04:00
Jeremy Stretch
1308839a41 Fixes #1458: Corrected permission name on prefix/VLAN roles list 2017-08-29 15:36:51 -04:00
Jeremy Stretch
6d849ad5f6 Deleted obsolete templates 2017-08-29 15:31:11 -04:00
Jeremy Stretch
fa95191792 Allowed assigning an IP address to either a device or a VM 2017-08-29 15:26:35 -04:00
Jeremy Stretch
e945aafd7b Added fitlers to return the verbose model name of an object 2017-08-29 15:10:52 -04:00
Jeremy Stretch
e9f75adddb Ditched VMInterface in favor of reusing dcim.Interface 2017-08-29 14:24:58 -04:00
Jeremy Stretch
3e4b66675b Rearranged circuits navigation dropdown 2017-08-24 12:51:50 -04:00
Jeremy Stretch
6f8d3f7a57 Fixes #1443: Fixed API validation error involving custom field data 2017-08-22 11:31:33 -04:00
Jeremy Stretch
eb994a691a Added style to BaseTable's empty_text 2017-08-21 17:38:27 -04:00
Jeremy Stretch
6ebd8e78c8 Added VMs to tenant view 2017-08-21 17:21:02 -04:00
Jeremy Stretch
daa947e7eb Added virtualization models to home/search 2017-08-21 17:17:01 -04:00
Jeremy Stretch
b117130aac Added icons 2017-08-21 17:02:35 -04:00
Jeremy Stretch
4587aba1d4 Added views to add/remove hosts to/from clusters 2017-08-21 16:53:36 -04:00
Jeremy Stretch
ae73e6f04d Removed task list formatting 2017-08-21 11:10:07 -04:00
Jeremy Stretch
5930a64203 Converted IPAddress.interface to a GenericForeignKey 2017-08-18 16:57:20 -04:00
Jeremy Stretch
97536c4e9b Genericized component edit/delete views 2017-08-18 14:43:11 -04:00
Jeremy Stretch
e81e33af38 Added views for VM interfaces 2017-08-18 14:37:19 -04:00
Jeremy Stretch
a7c56eab86 Moved DeviceComponentForm to utilities.forms 2017-08-18 13:10:19 -04:00
Jeremy Stretch
6e580a731e Added template names to device component create views 2017-08-18 12:29:39 -04:00
Jeremy Stretch
7843a6bf77 Fixed devicetype_id filter for DeviceType components 2017-08-18 10:27:43 -04:00
Jeremy Stretch
fa6d933544 Converted list of issue types to a task list 2017-08-17 15:14:20 -04:00
Jeremy Stretch
83085d08ca Revised the GitHub issue template 2017-08-17 15:11:55 -04:00
Jeremy Stretch
a02055e9b5 Reworked relationship between devices and clusters 2017-08-16 17:21:34 -04:00
Jeremy Stretch
9acd792abe Added virtualization filters 2017-08-16 17:00:17 -04:00
Jeremy Stretch
4ef55502b4 Updates for v2.1.3 and expanded VirtualMachine model 2017-08-16 15:25:33 -04:00
Jeremy Stretch
3009863877 Merge branch 'develop' into virtualization 2017-08-16 15:08:37 -04:00
Jeremy Stretch
70a6b171d0 Fixes #1429: Fixed uptime formatting on device status page 2017-08-16 11:20:52 -04:00
Jeremy Stretch
9a25b5458d Merge pull request #1430 from xmudrii/fix-docs
docs: web-server link to new ssl tutorials for 16.04
2017-08-15 21:39:59 -04:00
Marko Mudrinić
416aeb4072 docs: web-server link to new ssl tutorials for 16.04 2017-08-15 23:39:10 +02:00
Jeremy Stretch
0c1594b8f8 Post-release version bump 2017-08-15 15:53:10 -04:00
Jeremy Stretch
5be30bd278 Merge pull request #1428 from digitalocean/develop
Release v2.1.3
2017-08-15 15:52:34 -04:00
Jeremy Stretch
669ae104a4 Release v2.1.3 2017-08-15 15:50:51 -04:00
Jeremy Stretch
c37cfeb74f Fixed page titles in the browsable API 2017-08-15 15:30:45 -04:00
Jeremy Stretch
51b1da660a Fixes #1330: Raise validation error when assigning an unrelated IP as the primary IP for a device 2017-08-15 14:05:02 -04:00
Jeremy Stretch
c394985b1b Fixes #1421: Improved model validation logic for API serializers 2017-08-15 13:54:04 -04:00
Jeremy Stretch
04c300b8e2 Fixes #1420: Exclude virtual interfaces from device LLDP neighbors view 2017-08-15 11:07:26 -04:00
Jeremy Stretch
117f33afc5 Fixes #1419: Allow editing image attachments without re-uploading an image 2017-08-11 10:47:06 -04:00
Jeremy Stretch
3d92669df5 Corrected HTTP verb in API docs 2017-08-10 11:41:25 -04:00
Jeremy Stretch
63757af1a0 Expanded API overview documentation 2017-08-09 16:55:49 -04:00
Jeremy Stretch
e6a58b6700 Fixes #1415: Ignore leading/trailing semicolons in topology map device lists 2017-08-09 15:46:40 -04:00
Jeremy Stretch
8fb504c963 Tweaked installation docs to better accommodate Python 3 2017-08-09 14:19:33 -04:00
Jeremy Stretch
babe42ef35 Closes #1414: Selecting a site from the rack filters automatically updates the available rack groups 2017-08-09 13:59:25 -04:00
Jeremy Stretch
b67e3ff957 Implemented initial virtualization API 2017-08-08 16:33:34 -04:00
Jeremy Stretch
7557220d5d Fixes #1389: Avoid splitting carat/prefix on prefix list 2017-08-08 11:48:51 -04:00
Jeremy Stretch
50ccccc3f4 Merge pull request #1403 from huzichunjohn/issue_1400
Fixes #1400: Device interface shows twice on IP Addresses page
2017-08-08 08:23:14 -04:00
johnhu
b6afc68847 Fixes #1400: Device interface shows twice on IP Addresses page 2017-08-08 11:44:50 +00:00
Jeremy Stretch
d06813f528 Initial work on virtualization support (#142) 2017-08-04 17:02:52 -04:00
Jeremy Stretch
fa7b7288c9 Merge pull request #1398 from digitalocean/develop
Release v2.1.2
2017-08-04 10:54:29 -04:00
Jeremy Stretch
36d5debe74 Post-release version bump 2017-08-04 10:52:29 -04:00
Jeremy Stretch
a7d5fb5006 Release v2.1.2 2017-08-04 10:52:06 -04:00
Jeremy Stretch
dadfcd4f81 Tweaked navigation menu styling 2017-08-03 17:49:22 -04:00
Jeremy Stretch
236229ea7e Closes #992: Allow the creation of multiple services per device with the same protocol and port 2017-08-03 15:40:52 -04:00
Jeremy Stretch
6f0f3cc115 Fixes #1390: Fixed IndexError when viewing available IPs within large IPv6 prefixes 2017-08-03 10:09:37 -04:00
Jeremy Stretch
3aa072b437 Fixes #1388: Fixed server error when searching globally for IPs/prefixes (rolled back #1379) 2017-08-03 09:26:28 -04:00
Jeremy Stretch
9cc03aaa9a Merge pull request #1387 from digitalocean/develop
Release v2.1.1
2017-08-02 14:22:30 -04:00
Jeremy Stretch
930b15ae09 Post-release version bump 2017-08-02 14:20:43 -04:00
Jeremy Stretch
1c1f4068cd Release v2.1.1 2017-08-02 14:20:20 -04:00
Jeremy Stretch
b6690e0012 Closes #1368: Render reservations in rack elevations view 2017-08-02 13:33:44 -04:00
Jeremy Stretch
52e7f670f3 Deleted obsolete template 2017-08-02 13:17:34 -04:00
Jeremy Stretch
12d643694f Cleaned up title blocks 2017-08-02 13:15:28 -04:00
Jeremy Stretch
3876a96b67 Tweaked IP address layout 2017-08-02 12:55:21 -04:00
Jeremy Stretch
e50f234ba3 Closes #1379: Allow searching devices by interface MAC address in global search 2017-08-02 11:54:53 -04:00
Jeremy Stretch
fd6df8e52a Fixes #1385: Connected device API endpoint no longer requires authentication if LOGIN_REQUIRED=False 2017-08-02 11:17:57 -04:00
Jeremy Stretch
28225c7cbd Merge pull request #1386 from bdlamprecht/patch-1
Updated ldap.md
2017-08-02 10:56:41 -04:00
bdlamprecht
cab2929c10 Updated ldap.md
Add mapping of `email` field to `mail` value in LDAP.  Also remove duplicate entries of such mapping information.
2017-08-02 08:45:00 -06:00
Jeremy Stretch
52a490bf5d Fixes #461: Display a validation error when attempting to assigning a new child device to a rack face/position 2017-07-28 10:05:51 -04:00
Jeremy Stretch
f2c16fbf3c Closes #893: Allow filtering by null values for NullCharacterFields (e.g. return only unnamed devices) 2017-07-27 09:53:41 -04:00
Jeremy Stretch
65a633f42d Closes #1374: Added NAPALM_ARGS and NAPALM_TIMEOUT configiuration parameters 2017-07-26 11:47:59 -04:00
Jeremy Stretch
d2c6d79589 Closes #1375: Renamed NETBOX_USERNAME and NETBOX_PASSWORD configuration parameters to NAPALM_USERNAME and NAPALM_PASSWORD 2017-07-26 11:24:19 -04:00
Jeremy Stretch
f4a873745f Post-release version bump 2017-07-25 11:26:50 -04:00
Jeremy Stretch
1bda56ea23 Merge pull request #1372 from digitalocean/develop
Release v2.1.0
2017-07-25 11:21:44 -04:00
Jeremy Stretch
c7e9d90321 Release v2.1.0 2017-07-25 11:19:33 -04:00
Jeremy Stretch
7476194bd1 PEP8 fix 2017-07-25 10:58:28 -04:00
Jeremy Stretch
1770c85689 Fixes #1371: Extend DeviceSerializer.parent_device to include standard fields 2017-07-25 10:56:23 -04:00
Faidon Liambotis
4544893b4c Allow import/export of device types (#1347) 2017-07-25 17:21:34 +03:00
Faidon Liambotis
f30fa925ea Allow import/export of manufacturers (#1347) 2017-07-25 17:21:34 +03:00
Faidon Liambotis
beb91559e5 Allow import/export of rack groups (#1347) 2017-07-25 17:21:34 +03:00
Faidon Liambotis
fbc5e74318 Allow import/export of regions (#1347) 2017-07-25 17:21:34 +03:00
Jeremy Stretch
e364659c6e Tweaked NAPALM integration instructions 2017-07-25 10:17:28 -04:00
Jeremy Stretch
9e26198afe Added note about NAPALM integration 2017-07-25 10:09:44 -04:00
Jeremy Stretch
f9b6ddc230 Added "Migrating to Python3" to the docs index 2017-07-25 09:44:15 -04:00
vanderaaj
0991f94d06 How to migrate from Py2 to Py3 (#1355)
* How to migrate from Py2 to Py3

The commands done to migrate Ubuntu from Py2 to Py3.

* Update Migrating-to-Python3
2017-07-25 09:40:51 -04:00
Jeremy Stretch
32513083b1 Merge branch 'develop-2.1' into develop 2017-07-24 14:58:18 -04:00
Jeremy Stretch
336cdcddc5 PEP8 fix 2017-07-24 14:51:00 -04:00
Jeremy Stretch
4047c1a4e4 lsmodules() should only return native models 2017-07-24 14:34:01 -04:00
Jeremy Stretch
091cf390d2 Import constants from each app 2017-07-24 14:22:07 -04:00
Jeremy Stretch
05aaafc1cf Added docs for using the NetBox shell 2017-07-24 13:26:31 -04:00
Jeremy Stretch
5885b833cd Fixes #1362: Raise validation error when attempting to create an API key that's too short 2017-07-19 11:03:13 -04:00
Jeremy Stretch
106627da04 Fixes #1358: Correct VRF example values in IP/prefix import forms 2017-07-18 10:39:09 -04:00
Jeremy Stretch
d73ea54e08 Fixed table cell alignment for IP addresses 2017-07-17 13:55:20 -04:00
Jeremy Stretch
a45bfaf3da Hide/disable NAPALM tabs as appropriate 2017-07-17 13:29:11 -04:00
Jeremy Stretch
e85cc0d856 Removed legacy LLDP neighbors API endpoint 2017-07-17 13:21:38 -04:00
Jeremy Stretch
0f608f3a15 Added device config view 2017-07-17 13:19:25 -04:00
Jeremy Stretch
4ad5c6f864 Updated LLDP neighbors view to use NAPALM API 2017-07-17 13:05:11 -04:00
Jeremy Stretch
be47b6a6c0 Added device environmental status details 2017-07-17 12:58:13 -04:00
Jeremy Stretch
1f982c94ce Added an AJAX spinner 2017-07-17 11:41:39 -04:00
Jeremy Stretch
12472a2612 Live device status PoC 2017-07-14 16:07:28 -04:00
Jeremy Stretch
f6a8d32880 Initial work on NAPALM integration 2017-07-14 14:42:56 -04:00
Jeremy Stretch
bb2f86463e Upgraded jQuery to v3.2.1 2017-07-14 10:17:09 -04:00
Jeremy Stretch
e8dafc02f7 Merge branch 'develop' into develop-2.1
Conflicts:
	netbox/netbox/settings.py
2017-07-14 10:12:35 -04:00
Jeremy Stretch
0655834938 Post-release version bump 2017-07-14 10:11:04 -04:00
Jeremy Stretch
64a34ced72 Merge pull request #1346 from digitalocean/develop
Release v2.0.10
2017-07-14 10:09:16 -04:00
Jeremy Stretch
d0dc505220 Release v2.0.10 2017-07-14 10:07:21 -04:00
Jeremy Stretch
b2d3f3ff22 Tweaked page title 2017-07-14 10:01:59 -04:00
Jeremy Stretch
39730b6834 Optimized performance when editing/deleting objects in bulk 2017-07-13 17:39:28 -04:00
Jeremy Stretch
dd1991f2c6 Closes #838: Display details of all objects being edited/deleted in bulk 2017-07-13 16:31:47 -04:00
Jeremy Stretch
2f32e11f53 Fixes #1342: Allow designation of users and groups when creating/editing a secret role 2017-07-13 11:44:29 -04:00
Jeremy Stretch
280f55a875 Require django-tables2 v1.7+ 2017-07-13 11:39:59 -04:00
Jeremy Stretch
dc68be5abf Removed SearchTables; created DetailTables for models where needed 2017-07-12 16:42:45 -04:00
Jeremy Stretch
1ef90902bd Closes #1320: Remove checkbox from confirmation dialog 2017-07-12 14:53:52 -04:00
Jeremy Stretch
6f37e97c67 Fixes #1339: Fixed disappearing checkbox column under django-tables2 v1.7+ 2017-07-12 14:05:01 -04:00
Jeremy Stretch
e54c74d972 Fixes #1338: Allow importing prefixes with "container" status 2017-07-12 10:31:16 -04:00
Jeremy Stretch
af9fa85cc1 Fixes #1312: Catch error when attempting to activate a user key with an invalid private key 2017-07-12 10:06:13 -04:00
Jeremy Stretch
74828e1409 Fixes #1334: Fix server error when adding an interface to a device 2017-07-11 14:52:50 -04:00
Jeremy Stretch
dc77400ab1 Fixes #1333: Corrected label on is_console_server field of DeviceType bulk edit form 2017-07-11 14:36:59 -04:00
Jeremy Stretch
2d0638821d #1266: Exclude interfaces with existing connections or circuit terminations when creating a new connection 2017-07-10 12:44:16 -04:00
Jeremy Stretch
8a87d60f29 Closes #1269: Added circuit termination to interface serializer 2017-07-10 12:07:47 -04:00
Jeremy Stretch
530789b733 #1269: Reworked interface connection serialization 2017-07-10 11:52:36 -04:00
Jeremy Stretch
618d75566f Merge branch 'develop' into develop-2.1
Conflicts:
	netbox/netbox/settings.py
2017-07-10 09:48:20 -04:00
Jeremy Stretch
5f66893038 Post-release version bump 2017-07-10 09:44:34 -04:00
Jeremy Stretch
e05d379101 Merge pull request #1327 from digitalocean/develop
Release v2.0.9
2017-07-10 09:43:59 -04:00
Jeremy Stretch
41ea433e7c Release v2.0.9 2017-07-10 09:42:07 -04:00
Jeremy Stretch
bfd7881b7b Fixes #1325: Retain interface attachment when editing a circuit termination 2017-07-10 09:38:59 -04:00
Jeremy Stretch
1f9806a480 Fixes #1285: Enforce model validation when creating/editing objects via the API 2017-07-06 17:37:24 -04:00
Jeremy Stretch
5b43a108bc Merge branch 'develop' into develop-2.1
Conflicts:
	netbox/ipam/models.py
	netbox/netbox/settings.py
	netbox/templates/dcim/inc/interface.html
2017-07-06 13:27:13 -04:00
Jeremy Stretch
b253c8cc95 Fixes #1319: Fixed server error when attempting to create console/power connections 2017-07-06 13:20:53 -04:00
Jeremy Stretch
0fc9ed852e Fixed typo in example 2017-07-06 13:14:10 -04:00
Jeremy Stretch
175c1f2720 Post-release version bump 2017-07-05 14:36:25 -04:00
Jeremy Stretch
a355783377 Merge pull request #1316 from digitalocean/develop
Release v2.0.8
2017-07-05 14:36:08 -04:00
Jeremy Stretch
dafdbc9ddb Release v2.0.8 2017-07-05 14:34:46 -04:00
Jeremy Stretch
14f5204548 Fixes #1289: Retain inside NAT assignment when editing an IP address 2017-07-05 14:29:40 -04:00
Jeremy Stretch
5233463f0b Merge pull request #1315 from s11-charendt/develop
Preserve fileextension, regardless of upper or lower case on imageupload
2017-07-05 11:02:18 -04:00
Jeremy Stretch
1d4a416100 Fixes #1297: Allow passing custom field choice selection PKs as string-quoted integers 2017-07-05 11:00:43 -04:00
Jeremy Stretch
25ee796d5b Include instructions when displaying an inactive user key 2017-07-05 10:51:25 -04:00
Christian Harendt
e08107063a Preserve fileextension, regardless of upper or lower case on imageuploads 2017-07-04 14:26:35 +02:00
Jeremy Stretch
30d1605007 Closes #1246: Added ability to auto-create the next available IP address within a prefix 2017-06-30 16:51:31 -04:00
Jeremy Stretch
cd5a86bfcf Closes #1303: Highlight installed interface connections in green on device view 2017-06-29 13:35:54 -04:00
Jeremy Stretch
a23da9f867 PEP8 fixes 2017-06-28 16:25:36 -04:00
Jeremy Stretch
d5bb37b552 #1246: Initial work on an API endpoint to retrieve available IPs for a prefix 2017-06-28 16:23:17 -04:00
Jeremy Stretch
97b67d0f93 Fixes #1299: Corrected permission to add a service to a device 2017-06-28 12:05:26 -04:00
Jeremy Stretch
3f82be7192 Closes #1298: Calculate prefix utilization based on its status (container or non-container) 2017-06-26 17:36:24 -04:00
Jeremy Stretch
adfcb5f7b6 Fixes #1295: Docstring typo 2017-06-26 09:31:20 -04:00
Jeremy Stretch
5940feb64b Closes #1121: Added asset_tag and description fields to inventory items 2017-06-23 17:05:37 -04:00
Jeremy Stretch
229e6809d8 Closes #1041: Added enabled and MTU fields to the interface model 2017-06-23 14:04:15 -04:00
Jeremy Stretch
87e5687d03 Closes #1203: Implemented query filters for all models 2017-06-19 16:10:18 -04:00
Jeremy Stretch
5aba1d9aec Fixes #1288: Corrected permission name for deleting image attachments 2017-06-19 09:20:03 -04:00
Jeremy Stretch
68ebe85a98 Closes #1218: Added IEEE 802.11 wireless interface types 2017-06-16 17:52:09 -04:00
Jeremy Stretch
789ac5dfd4 Combined mgmt and non-mgmt interfaces into same list on device and device type views 2017-06-16 17:13:33 -04:00
Jeremy Stretch
ceb8fee0cc Moved constant definitions from models.py to constants.py 2017-06-16 16:01:44 -04:00
Jeremy Stretch
421270f4a6 Renamed IP address status 'virtual' to 'VIP' 2017-06-16 15:37:46 -04:00
Jeremy Stretch
afdf5750b5 Fixes #1279: Fix primary_ip assignment during IP address import 2017-06-16 12:45:42 -04:00
Jeremy Stretch
ea869d4ffc Fixes #1282: Fixed tooltips on "mark connected/planned" toggle buttons for device connections 2017-06-16 10:02:14 -04:00
Jeremy Stretch
9d89eed873 Fixes #1281: Show LLDP neighbors tab on device view only if necessary conditions are met 2017-06-16 09:32:58 -04:00
Jeremy Stretch
c00eea7991 Post-release version bump 2017-06-15 14:28:35 -04:00
Jeremy Stretch
88239e0b0d Merge pull request #1278 from digitalocean/develop
Release v2.0.7
2017-06-15 14:26:38 -04:00
Jeremy Stretch
ba8f48af65 Merge branch 'develop' into develop-2.1
Conflicts:
	netbox/netbox/settings.py
2017-06-15 14:26:02 -04:00
Jeremy Stretch
9930e2745f Release v2.0.7 2017-06-15 14:22:16 -04:00
Jeremy Stretch
da3879e928 Fixes #1275: Raise validation error on prefix import when multiple VLANs are found 2017-06-15 14:13:20 -04:00
Jeremy Stretch
7195b7c803 Closes #626: Added bulk disconnect function for console/power/interface connections on device view 2017-06-15 14:01:49 -04:00
Jeremy Stretch
9b082eea14 Fixes #1274: Exclude unterminated circuits from topology maps 2017-06-15 10:05:14 -04:00
Jeremy Stretch
a16218b311 Fixes #1273: Corrected status choices in IP address import form 2017-06-14 16:22:49 -04:00
Jeremy Stretch
f427c00d94 Closes #819: Implemented IP address functional roles 2017-06-14 16:11:13 -04:00
Jeremy Stretch
8bcd8c404d Closes #1141: Include VRF name and RD in form selections 2017-06-14 15:00:27 -04:00
Jeremy Stretch
4d7f9c42c8 Version bump for v2.1 2017-06-14 14:55:59 -04:00
Jeremy Stretch
29a71fd903 #1265: Improved livesearch UI Javascript 2017-06-14 14:50:12 -04:00
Jeremy Stretch
cd263484c3 Fixes #1079: Order interfaces naturally via API 2017-06-14 14:34:14 -04:00
Jeremy Stretch
fcacac7c6f Fixes #1265: Fix console/power/interface connection validation when selecting a device via live search 2017-06-14 13:00:36 -04:00
Jeremy Stretch
78d74261e9 Fixes #1266: Prevent termination a circuit to an already-connected interface 2017-06-14 10:57:43 -04:00
Jeremy Stretch
16d694734b Fixes #1268: Fix CSV import error under Python 3 2017-06-14 09:55:52 -04:00
Jeremy Stretch
252ab0fbab Fixes #1238: Fix error when editing an IP with a NAT assignment which has no assigned device 2017-06-13 16:57:25 -04:00
Jeremy Stretch
8eb9c451a1 Renamed AddViews to CreateViews for consistency 2017-06-13 16:48:21 -04:00
Jeremy Stretch
469c52be28 Fixes #1263: Differentiate add and edit permissions for objects 2017-06-13 16:41:57 -04:00
Jeremy Stretch
54fa51eeff Post-release version bump 2017-06-13 15:55:58 -04:00
Jeremy Stretch
5456af6867 Removed 'update-alternatives' from Python3 instructions 2017-06-13 14:28:38 -04:00
Jeremy Stretch
180446c34d Removed Debian/RHEL references from installation docs 2017-06-12 10:06:19 -04:00
Jeremy Stretch
5c63a499d5 Merge pull request #1259 from digitalocean/develop
Release v2.0.6
2017-06-12 09:51:15 -04:00
Jeremy Stretch
3a2c5b318a Release v2.0.6 2017-06-12 09:44:09 -04:00
Jeremy Stretch
cfff69a715 Closes #1180: Simplified the process of finding related devices when viewing a device 2017-06-09 17:04:09 -04:00
Jeremy Stretch
08883d86ef Closes #913: Added headers to object CSV exports 2017-06-09 16:24:59 -04:00
Jeremy Stretch
8a849ebeff Closes #990: Enable logging configuration in configuration.py 2017-06-09 15:03:10 -04:00
Jeremy Stretch
05a796faf1 Closes #704: Allow filtering VLANs by group when editing prefixes 2017-06-09 14:15:12 -04:00
Jeremy Stretch
9e1d03b383 Formatting cleanup 2017-06-09 12:19:32 -04:00
Jeremy Stretch
0a929f2971 Fixes #1253: Improved upgrade.sh to allow forcing Python2 2017-06-09 12:13:47 -04:00
Jeremy Stretch
7878992570 First stab at an interactive shell which pre-imports all models 2017-06-08 16:38:25 -04:00
Jeremy Stretch
4f95926cbd Added utilization percetange to aggregate and prefix views 2017-06-08 12:48:49 -04:00
Jeremy Stretch
f3e997ea39 Closes #40: Added IP utilization graph to prefix list 2017-06-08 12:37:25 -04:00
Jeremy Stretch
2b921c21ff Post-release version bump 2017-06-08 10:12:39 -04:00
Jeremy Stretch
50496b1a59 Merge pull request #1251 from digitalocean/develop
Release v2.0.5
2017-06-08 10:10:41 -04:00
Jeremy Stretch
9736d63577 Release v2.0.5 2017-06-08 10:05:26 -04:00
Jeremy Stretch
13add414c4 Fixed formatting and typos 2017-06-08 10:02:16 -04:00
Jeremy Stretch
b032bc13db Merge pull request #1080 from bellwood/patch-2
Enhance LDAP documentation
2017-06-08 09:55:17 -04:00
Jeremy Stretch
aaad428438 Merge pull request #1249 from feuerrot/patch-1
upgrading.md: fix typo
2017-06-08 09:17:52 -04:00
Markus Witt
203895fc7e upgrading.md: fix typo 2017-06-08 10:58:30 +02:00
Jeremy Stretch
aab1fab445 PEP8 fix 2017-06-07 15:56:59 -04:00
Jeremy Stretch
e06221bc89 Merge branch 'import_headers' into develop 2017-06-07 15:54:59 -04:00
Jeremy Stretch
26a13edcf3 Layout tweaks 2017-06-07 15:54:48 -04:00
Jeremy Stretch
65b6fe576f Converted device fields to use FlexibleModelChoiceField; misc cleanup 2017-06-07 15:51:11 -04:00
Jeremy Stretch
4671829ad8 Removed obsolete validation from InterfaceConnectionCSVForm 2017-06-07 15:33:10 -04:00
Jeremy Stretch
293be752ca Form cleanup and fixed child device import 2017-06-07 15:30:28 -04:00
Jeremy Stretch
0a6e4f31d5 Updated CSVForm validation 2017-06-07 14:19:08 -04:00
Jeremy Stretch
e6c4ce51f7 Replaced all CSVForm ChoiceFields with CSVChoiceField 2017-06-07 13:22:06 -04:00
Jeremy Stretch
3924063060 Converted ConnectionStatusCSVField to a ChoiceField 2017-06-06 22:35:41 -04:00
Jeremy Stretch
d122f9f700 Added dynamic examples for CSV form fields 2017-06-06 17:27:26 -04:00
Brian Ellwood
d0649ba815 Update ldap.md
Wrap code in code block
2017-06-05 20:37:09 -04:00
Brian Ellwood
1ec09270a7 Update ldap.md
Capitalization
2017-06-05 20:35:05 -04:00
Jeremy Stretch
1ddd7415cb Replaced old CSVDataField 2017-06-05 16:16:23 -04:00
Jeremy Stretch
ec9d0d4008 Implemented ConnectionStatusCSVField 2017-06-05 16:12:51 -04:00
Jeremy Stretch
08c8bd3049 Renamed new import view to BulkImportView 2017-06-05 15:53:41 -04:00
Jeremy Stretch
2520d9f400 Converted console/power import views to new scheme 2017-06-05 15:53:03 -04:00
Jeremy Stretch
0e863ff9ca Converted interface connections import view to new scheme 2017-06-05 15:04:23 -04:00
Jeremy Stretch
1b78f54c6b Merge pull request #1245 from digitalocean/fix-secret-device-filter
Fixes #1244
2017-06-03 00:03:27 -04:00
Zach Moody
b732c24ec4 Fixes #1244 2017-06-02 16:33:49 -05:00
Jeremy Stretch
af604aba31 Converted secrets import view to new scheme 2017-06-02 17:23:41 -04:00
Jeremy Stretch
c82658440f Converted IPAM import views to new scheme 2017-06-02 16:07:11 -04:00
Jeremy Stretch
7e660d4d8e Converted site/rack/device import views to new scheme 2017-06-02 14:49:25 -04:00
Jeremy Stretch
4a8147f8a5 Converted circuits import views to new scheme 2017-06-02 13:40:52 -04:00
Jeremy Stretch
583830c652 #1190: Allow partial string matching when searching on custom fields 2017-06-01 16:57:32 -04:00
Jeremy Stretch
95fdb549d7 Fixes #1243: Catch ValueError in IP-based object filters 2017-06-01 16:13:07 -04:00
Jeremy Stretch
a598f0e632 Initial work on #655: CSV import headers 2017-05-31 17:40:11 -04:00
Jeremy Stretch
293dbd8a8b Fixes #1226: Improve validation for custom field values submitted via the API 2017-05-31 14:09:57 -04:00
Jeremy Stretch
f03a378ce0 Fixes #1239: Fix server error when creating VLANGroup via API 2017-05-31 11:50:03 -04:00
Jeremy Stretch
6aae8aee5b Closes #1237: Enabled setting limit=0 to disable pagination in API requests; added MAX_PAGE_SIZE configuration setting 2017-05-30 23:24:21 -04:00
Jeremy Stretch
6d908d3e79 Fixes #1236: Truncate rack names in elevations list; add facility ID 2017-05-30 13:13:01 -04:00
Jeremy Stretch
d5016c7133 Fixes #1235: Fix permission name for adding/editing inventory items 2017-05-30 13:03:25 -04:00
Jeremy Stretch
b5a1b692bd Fixes #1225: Fixed border on empty circuits table on provider view 2017-05-26 10:08:03 -04:00
Jeremy Stretch
834c396a22 Tweaked upgrade script to prefer pip3/python3 if present 2017-05-26 09:55:22 -04:00
Jeremy Stretch
bc18d241e8 Post-release version bump 2017-05-25 14:46:34 -04:00
Jeremy Stretch
f7b0d22f86 Merge pull request #1230 from digitalocean/develop
Release v2.0.4
2017-05-25 14:45:13 -04:00
Jeremy Stretch
5a1877087f Release v2.0.4 2017-05-25 14:42:58 -04:00
Jeremy Stretch
50462ec15d Added notes to discourage the prepending of arbitrary tags to issue titles 2017-05-25 14:38:33 -04:00
Jeremy Stretch
1dd5e2c926 Fixes #1229: Fix validation error on forms where API search is used 2017-05-25 14:33:50 -04:00
Jeremy Stretch
ebddc46bc0 PEP8 fix 2017-05-24 14:22:37 -04:00
Jeremy Stretch
138cbf9761 Created migrations for transition to Unicode literals 2017-05-24 14:18:52 -04:00
Jeremy Stretch
f21c6bca00 Import unicode_literals 2017-05-24 11:33:11 -04:00
Jeremy Stretch
9aad8a7774 Fixes #1219: Fix image attachment URLs when BASE_PATH is set 2017-05-24 10:34:01 -04:00
Jeremy Stretch
68b6c7d886 Fixes #1210: Fix TemplateDoesNotExist errors on browsable API views 2017-05-24 09:40:24 -04:00
Jeremy Stretch
1c489e57cc Added a warning to note "untracked migrations" warnings during an upgrade 2017-05-23 22:36:40 -04:00
Jeremy Stretch
6719578f14 Fixes #1212: Allow assigning new VLANs to global VLAN groups 2017-05-23 22:23:50 -04:00
Jeremy Stretch
d5587de316 Fixes #1213: Corrected table header ordering links 2017-05-23 22:15:13 -04:00
Jeremy Stretch
77f28e3441 Fixes #1214: Add status to list of required fields on child device import form 2017-05-23 22:12:17 -04:00
Jeremy Stretch
3fa63b774e Converted home view to a CBV 2017-05-19 16:03:51 -04:00
Jeremy Stretch
713c7cd8e3 Cleaned up 500 error template 2017-05-19 16:03:04 -04:00
Jeremy Stretch
e6b4d87939 Converted all user views to CBVs 2017-05-19 15:47:19 -04:00
Jeremy Stretch
27c94d9874 Fixes #1206: Fix redirection in admin UI after activating secret keys when BASE_PATH is set 2017-05-19 13:23:17 -04:00
Jeremy Stretch
eece8a0e26 Fixes #1207: Include nested LAG serializer when showing interface connections (API) 2017-05-19 12:59:27 -04:00
Jeremy Stretch
fb85867d72 Converted all object views to class-based views 2017-05-18 17:00:57 -04:00
Jeremy Stretch
c454bfcd84 Fixed incorrect message 2017-05-18 14:53:35 -04:00
Jeremy Stretch
ad95b86fdd Merge pull request #1201 from digitalocean/develop
Release v2.0.3
2017-05-18 14:37:19 -04:00
Jeremy Stretch
769232f368 Post-release version bump 2017-05-18 14:32:11 -04:00
Jeremy Stretch
9cf10eecd1 Release v2.0.3 2017-05-18 14:31:48 -04:00
Jeremy Stretch
f927d5b8f5 Closes #1198: Allow filtering unracked devices on device list 2017-05-18 14:27:07 -04:00
Jeremy Stretch
7fa696dace Fixes #1195: Unable to create an interface connection when searching for peer device 2017-05-18 13:33:26 -04:00
Jeremy Stretch
feac93389c Fixes #1200: Form validation error when connecting power ports to power outlets 2017-05-18 12:11:14 -04:00
Jeremy Stretch
f7969d91b3 Fixes #1199: Bulk import of secrets does not prompt user to generate a session key 2017-05-18 09:17:41 -04:00
Jeremy Stretch
92aafb9043 Added WSGIPassAuthorization to example Apache config 2017-05-17 17:23:08 -04:00
Jeremy Stretch
f9328d53b4 Fixes #1197: Fixed status assignment during bulk import of devices, prefixes, IPs, and VLANs 2017-05-17 17:16:02 -04:00
Jeremy Stretch
f1cbc7da33 Fixes #1157: Hide nav menu search bar on small displays 2017-05-17 16:00:46 -04:00
Jeremy Stretch
01becd21de Closes #1196: Added a lag_id filter to the API interfaces view 2017-05-17 14:43:44 -04:00
Jeremy Stretch
7768b94279 Fixes #1188: Serialize interface LAG as nested objected (API) 2017-05-17 14:32:39 -04:00
Jeremy Stretch
3bc51c8e69 Fixes #1191: Bulk selection of IPs under a prefix incorrect when "select all" is used 2017-05-17 14:23:08 -04:00
Jeremy Stretch
d206be91d5 Fixes #1130: Added zlib1g-dev to Ubuntu/Debian packages list 2017-05-17 12:48:31 -04:00
Jeremy Stretch
6e69c9e375 Restored the option to hide the paginator on panel tables 2017-05-17 12:18:32 -04:00
Jeremy Stretch
f2846af4ec Fixes #1189: Enforce consistent ordering of objects returned by a global search 2017-05-17 12:16:57 -04:00
Jeremy Stretch
657eed1dc9 Merge pull request #1185 from ryanmerolle/patch-1
Added vagrant alternative installation link
2017-05-16 16:53:01 -04:00
Jeremy Stretch
e351ab0171 Fixes #1186: Corrected VLAN edit form so that site assignment is not required 2017-05-16 16:30:28 -04:00
Jeremy Stretch
779446da64 Fixes #1187: Fixed table pagination by introducing a custom table template 2017-05-16 16:19:55 -04:00
ryanmerolle
2ff0d7aa83 Added vagrant alternative installation link 2017-05-16 07:13:05 -04:00
Jeremy Stretch
7ceb64b57b Post-release version bump 2017-05-15 13:24:37 -04:00
Jeremy Stretch
43e1e0dbc8 Merge pull request #1181 from digitalocean/develop
Release v2.0.2
2017-05-15 13:23:33 -04:00
Jeremy Stretch
a1c12cfd77 Release v2.0.2 2017-05-15 13:19:18 -04:00
Jeremy Stretch
aa6ca21a34 PEP8 fix 2017-05-15 13:18:49 -04:00
Jeremy Stretch
a49521d683 #1177: Render planned connections as dashed lines on topology maps 2017-05-15 13:11:20 -04:00
Jeremy Stretch
3be6e5b015 Closes #1179: Adjust topology map text color based on node background 2017-05-15 12:56:16 -04:00
Jeremy Stretch
ca1725b98c Fixes #1178: Fix API representation of connected interface's form factor 2017-05-15 11:03:11 -04:00
Jeremy Stretch
d11dfe2ced Closes #1137: Allow filtering devices list by rack 2017-05-12 22:41:27 -04:00
Jeremy Stretch
ab30ba1e1b Fixed dynamic selection of device type filter on devices list 2017-05-12 22:20:21 -04:00
Jeremy Stretch
7f23cb9bf5 Closes #1122: Include NAT inside IPs in IP address list 2017-05-12 22:11:20 -04:00
Jeremy Stretch
c9d3cf301e Fixes #1173: Tweak interface manager to fall back to naive ordering 2017-05-12 16:10:18 -04:00
Jeremy Stretch
67282882fa Fixed RelatedObjectDoesNotExist error when trying to create a new device 2017-05-12 15:55:18 -04:00
Jeremy Stretch
73bf4f45c3 Adapted model get_display_name() to better handle unsaved instances 2017-05-12 15:31:34 -04:00
Jeremy Stretch
66ae62fb91 Closes #1172: Linkify racks in side-by-side elevations view 2017-05-12 14:19:37 -04:00
Jeremy Stretch
8bae804508 Closes #1170: Include A and Z sites for circuits in global search results 2017-05-12 12:12:47 -04:00
Jeremy Stretch
d87acc97c3 Fixes #1171: Allow removing site assignment when bulk editing VLANs 2017-05-12 12:06:37 -04:00
Jeremy Stretch
f9b2c59974 Moved tenancy to separate panel on bulk IP creation form 2017-05-12 12:04:06 -04:00
Jeremy Stretch
a870a3b918 Fixes #1166: Re-implemented bulk IP address creation 2017-05-12 12:00:26 -04:00
Jeremy Stretch
008ed34553 Fixes #1168: Total count of obejcts missing from list view paginator 2017-05-11 23:30:23 -04:00
Jeremy Stretch
e239045688 PEP8 fixes 2017-05-11 17:54:43 -04:00
Jeremy Stretch
ed80bfaf02 Fixed selector initializations for TenancyForms 2017-05-11 17:52:23 -04:00
Jeremy Stretch
473b35f9a3 Added tenant_group/tenant form section to all objects with tenancy 2017-05-11 17:35:20 -04:00
Jeremy Stretch
45bb7eec0b Corrected queryset filter when parent_field is None 2017-05-11 17:20:50 -04:00
Jeremy Stretch
58bb029666 Closes #1167: Introduced ChainedModelChoiceFields 2017-05-11 16:30:16 -04:00
Jeremy Stretch
0f97478b55 Fixes #1161: Fix "add another" behavior when creating an API token 2017-05-10 22:22:49 -04:00
Jeremy Stretch
9efa70a551 Fixes #1159: Only superusers can see "edit IP" buttons on the device interfaces list 2017-05-10 16:02:50 -04:00
Jeremy Stretch
ed65721085 Fixes #1160: Linkify secrets and tenants in global search results 2017-05-10 13:16:33 -04:00
Jeremy Stretch
83688fceb7 Fixes #1158: Exception thrown when creating a device component with an invalid name 2017-05-10 11:23:54 -04:00
Jeremy Stretch
088f75ba0c Added client_max_body_size to nginx config; removed statement disabling access logging 2017-05-10 11:11:03 -04:00
Jeremy Stretch
188cfa08a9 Post-release version bump 2017-05-09 22:48:14 -04:00
Jeremy Stretch
f731900e2f Merge pull request #1154 from digitalocean/develop
Release v2.0.1
2017-05-09 22:47:52 -04:00
Jeremy Stretch
b89bd24bed Release v2.0.1 2017-05-09 22:41:37 -04:00
Jeremy Stretch
effda88b51 Fixes #1153: UnicodeEncodeError when searching for non-ASCII characters on Python 2 2017-05-09 22:40:46 -04:00
Jeremy Stretch
3844f70a4d Fixes #1152: Unable to edit user keys 2017-05-09 17:53:37 -04:00
Jeremy Stretch
8e333757f9 Fixes #1150: Error when uploading image attachments with Unicode names under Python 2 2017-05-09 17:12:45 -04:00
Jeremy Stretch
0fb12bcc9c Fixes #1151: name 'escape' is not defined 2017-05-09 17:06:17 -04:00
Jeremy Stretch
44d78ef92a Fixes #1149: Port list does not populate when creating a console or power connection 2017-05-09 17:01:43 -04:00
Jeremy Stretch
ebb6729a26 Post-release version bump 2017-05-09 15:13:40 -04:00
Jeremy Stretch
b1bcaa33e7 Merge pull request #1148 from digitalocean/develop
Release v2.0.0
2017-05-09 15:09:28 -04:00
Jeremy Stretch
a35f8bddde PEP8 fix 2017-05-09 14:44:32 -04:00
Jeremy Stretch
8fbe7ba742 Release v2.0.0! 2017-05-09 14:29:11 -04:00
Jeremy Stretch
f039b0b6e9 Closes #960: Added form factor for Juniper VCP interfaces 2017-05-09 12:00:49 -04:00
Jeremy Stretch
9ad9ef7957 Fixed incorrect API URL in IPAddressForm 2017-05-09 11:11:30 -04:00
Jeremy Stretch
5c7db04465 Closes #853: Add 'status' field to device bulk import form 2017-05-09 10:25:30 -04:00
Jeremy Stretch
838105fb65 Merging v2.0 development into mainline (#1145)
Merging v2.0 development into mainline
2017-05-08 15:06:57 -04:00
Jeremy Stretch
5ca87c0f20 Merge branch 'develop' into v2-develop 2017-05-08 15:02:06 -04:00
Jeremy Stretch
af4edff370 Related to #1144: Allow multiple status selections when filtering device list 2017-05-08 14:56:25 -04:00
Jeremy Stretch
f40c048475 Fixes #1144: Allow multiple status selections for Prefix, IP address, and VLAN filters 2017-05-08 14:32:29 -04:00
Jeremy Stretch
77247cccbe Closes #154: Expand device status field options 2017-05-08 13:55:19 -04:00
Jeremy Stretch
fcfcd77bfd Moved LAG members list to the description column 2017-05-05 15:37:42 -04:00
Jeremy Stretch
b3667befb4 Removed reduntant title block 2017-05-05 15:24:58 -04:00
Jeremy Stretch
a6cb0e0a96 Updated console/power connection icons 2017-05-03 17:24:57 -04:00
Jeremy Stretch
c047f943de Fixes #403: Record console/power/interface connects and disconnects as user actions 2017-05-03 17:12:34 -04:00
Jeremy Stretch
79089cc47e Introduced an object import template 2017-05-03 15:41:36 -04:00
Jeremy Stretch
3c631902e1 Closes #1100: Add a "view all" link to completed bulk import views is_pool for prefixes 2017-05-03 15:27:26 -04:00
Jeremy Stretch
379c24a012 Fixed typo in template 2017-05-03 14:32:27 -04:00
Brian Ellwood
4035b87693 Allow responsive tables (#1124)
* Make tables responsive #1115

Resolves #1115
2017-05-03 14:30:05 -04:00
Jeremy Stretch
11d1a8c3cf Merge pull request #1128 from digitalocean/readme-branches
Fix misleading build matrix
2017-05-03 14:21:07 -04:00
Jeremy Stretch
7eb9c8265c Fixes #1132: Prompt user to unlock session key when importing secrets 2017-05-03 11:47:28 -04:00
Matt Layher
572beb2311 Fix misleading build matrix
At one point, I had intended to have a matrix of build badges for each different branch and Python version combination.  It seems this is not possible with Travis.

This change replaces "python 2.7" with "status" and clarifies that both Python 2.7 and 3.5 are tested, but Python 3.5 is recommended.
2017-05-02 20:39:43 -04:00
Jeremy Stretch
d861d8bfb8 Fixes #1118: Allow designating an IP as primary for a device while editing the IP 2017-05-02 16:46:23 -04:00
Jeremy Stretch
6791ff6192 Fixes #1125: Include MAC addresses on a device's interface list 2017-05-02 15:01:27 -04:00
Jeremy Stretch
9d9de6b2a3 Fixes #1126: Fix error when editing a user key via admin UI 2017-05-02 14:50:36 -04:00
Jeremy Stretch
1f7ef15ad1 Fixes #1116: Correct object links on recursive deletion error 2017-05-02 11:43:11 -04:00
Jeremy Stretch
16c582ec7a Enable stale .pyc cleanup in upgrade.sh 2017-05-01 16:53:51 -04:00
Jeremy Stretch
de58d0ecca Fixes #1114: Suppress OSError when attempting to access a delete image attachment 2017-04-28 14:26:17 -04:00
Jeremy Stretch
010f6c7f1a Fixes #1113: Fixes server error when attempting to delete an image attachment 2017-04-28 14:05:02 -04:00
Jeremy Stretch
aea5612c39 Closes #1110: Expand bulk edit forms to include boolean fields (e.g. toggle is_pool for prefixes) 2017-04-28 12:32:27 -04:00
Jeremy Stretch
b8b912bdd5 Post-release version adjustment 2017-04-27 15:42:24 -04:00
Jeremy Stretch
e4ca88726e Release v2.0-beta3 2017-04-27 15:37:15 -04:00
Jeremy Stretch
616f109671 Merge branch 'develop' into v2-develop
Conflicts:
	netbox/ipam/forms.py
2017-04-27 15:29:40 -04:00
Jeremy Stretch
8e0580ff96 Improved upgrade script 2017-04-27 14:42:52 -04:00
Jeremy Stretch
4b2e7620dd Switched user nav menu with search form 2017-04-27 13:27:16 -04:00
Jeremy Stretch
b82f25c503 Merge branch 'writable-custom-fields' into v2-develop 2017-04-27 13:05:44 -04:00
Jeremy Stretch
c174c0cc6d Converted all necessary serializers to CustomFieldModelSerializers 2017-04-27 12:50:43 -04:00
Jeremy Stretch
117da337c7 Corrected tests and improved validation 2017-04-27 12:46:04 -04:00
Jeremy Stretch
01da46f753 Fixes #1107: Corrected exception on creating/deleting image attachments 2017-04-27 11:32:08 -04:00
Jeremy Stretch
d17efce4f5 Fixes #1111: Correct database ordering of SessionKey model 2017-04-27 11:27:34 -04:00
Jeremy Stretch
e7a6d1f532 Fixes #1104: Fix VLAN assignment on prefix import 2017-04-26 13:28:09 -04:00
Jeremy Stretch
f643f2c601 Fixes #1103: Correct handling of validation errors when creating IP addresses in bulk 2017-04-26 13:21:38 -04:00
Jeremy Stretch
480faa6461 Removed deprecated IPAddressAssignForm 2017-04-26 13:03:18 -04:00
Jeremy Stretch
1fa084b6be Fixes #1101: Fix AJAX scripting for device component selection forms 2017-04-26 12:53:14 -04:00
Jeremy Stretch
1c86b00b5c Added custom field API tests 2017-04-25 14:53:18 -04:00
Jeremy Stretch
10823e1c37 Got rudimentary custom field creates/updates working 2017-04-25 13:00:28 -04:00
Jeremy Stretch
f73693206f Merge branch 'develop' into v2-develop
Conflicts:
	netbox/circuits/models.py
	netbox/netbox/settings.py
	upgrade.sh
2017-04-21 15:07:48 -04:00
Jeremy Stretch
861c8b29c0 Post-release version bump 2017-04-21 14:56:36 -04:00
Jeremy Stretch
17873706b7 Merge pull request #1094 from digitalocean/develop
Release v1.9.6
2017-04-21 14:52:53 -04:00
Jeremy Stretch
5037046624 Release v1.9.6 2017-04-21 14:47:31 -04:00
Jeremy Stretch
5c0614d656 #1090: Python3 tweaks for installation on CentOS 2017-04-21 14:37:47 -04:00
Jeremy Stretch
697866d1ba #1090: Tweaked docs for Python3 on Ubuntu 2017-04-21 13:30:18 -04:00
Jeremy Stretch
38d826d152 Fixes #1092: Increase randomness in SECRET_KEY generation tool 2017-04-21 10:32:10 -04:00
Jeremy Stretch
13cc29cd8c Closes #951: Provide a side-by-side view of rack elevations 2017-04-20 13:07:22 -04:00
Jeremy Stretch
401357b8cb Closes #1084: Include custom fields when creating IP addresses in bulk 2017-04-19 14:50:58 -04:00
Jeremy Stretch
599e1bb220 Fixes #1071: Protect assigned circuit termination when an interface is deleted 2017-04-19 13:19:30 -04:00
Jeremy Stretch
864fa17b75 Closes #1008: Moved Docker components into their own repository 2017-04-19 10:58:42 -04:00
Jeremy Stretch
a98c9ed0af Corrected invalid API URL name 2017-04-17 15:52:23 -04:00
Jeremy Stretch
8032aa1ad9 Fixes #1078: Increase default limit for number of objects returned by web form API call 2017-04-17 15:50:00 -04:00
bellwood
5ff4e3b194 Enhance LDAP documentation
Incorporating @marvnrawley's enhancements from #518
2017-04-13 17:03:58 -04:00
Jeremy Stretch
b01bf6089c Merge branch 'develop' into v2-develop
Conflicts:
	netbox/dcim/forms.py
	netbox/dcim/views.py
	netbox/ipam/forms.py
	netbox/templates/_base.html
	netbox/utilities/views.py
2017-04-13 15:42:50 -04:00
Jeremy Stretch
f9a33bfc14 Fixes #1074: Require ncclient 0.5.3 (Python 3 fix) 2017-04-13 15:34:35 -04:00
Jeremy Stretch
610b412506 #878: Layout tweaks 2017-04-13 15:09:08 -04:00
Jeremy Stretch
09000ad9b3 Closes #1001: Merged IP interface assignment into ipam.IPAddressForm 2017-04-13 14:54:17 -04:00
Jeremy Stretch
f70f0f8d62 Improved handling of return_url for object edit/delete views; removed manual definitions of initial data fields 2017-04-13 13:11:23 -04:00
Jeremy Stretch
d5c3f9e780 #878: Show assigned IP addresses in device interfaces list 2017-04-12 22:02:23 -04:00
Jeremy Stretch
b42dab3eef Differentiate between LAG and virtual interfaces in device interface list 2017-04-12 16:06:36 -04:00
Jeremy Stretch
7cbea49c2d Fixes #1072: Order LAG interfaces naturally on bulk interface edit form 2017-04-12 15:51:14 -04:00
Jeremy Stretch
6dcc5a1169 Merge pull request #1070 from bellwood/patch-1
Python3 fixes for CentOS/RHEL
2017-04-12 15:25:36 -04:00
bellwood
53129125dd Python3 fixes for CentOS/RHEL
1) python3 should be python34
2) python34-pip does does exist, you must install python34-setuptools and then: easy_install-3.4 pip
2017-04-12 09:42:48 -04:00
Jeremy Stretch
2d52b9fb39 Fixes #1059: Allow filtering of interface connections via API 2017-04-10 16:15:36 -04:00
Jeremy Stretch
863cbb785d Merge pull request #1064 from eliezerlp/v2-develop
Pointing Dockerfile to 'v2-beta' branch instead of a particular tag
2017-04-10 10:57:18 -04:00
Jeremy Stretch
ba1a4f06ff Replace tabs with spaces 2017-04-10 10:55:05 -04:00
Jeremy Stretch
cf5be85dad Closes #1061: Escape all messages by default (complements #1062) 2017-04-10 10:54:35 -04:00
Eliezer Paiewonsky
d21b67446f Pointing Dockerfile to 'v2-beta' branch
Was pointing to a particular tag instead.
2017-04-10 10:27:32 -04:00
Jeremy Stretch
3b48a270fc Merge pull request #1062 from asteinhauser/develop
XSS flaw bugfix
2017-04-10 10:14:31 -04:00
Anthony Steinhauser
105e9da866 XSS flaw bugfix 2017-04-10 16:00:22 +02:00
Jeremy Stretch
d3b16ba443 Fixes #1057: Corrected VLAN validation during prefix import 2017-04-07 14:50:08 -04:00
Jeremy Stretch
57fc6a3f50 Merge branch 'develop' into v2-develop
Conflicts:
	netbox/netbox/settings.py
	netbox/netbox/urls.py
	requirements.txt
2017-04-06 17:01:13 -04:00
Jeremy Stretch
abc51fdc5d Post-release version bump 2017-04-06 16:36:42 -04:00
Jeremy Stretch
e0ad2b4555 Merge pull request #1054 from digitalocean/develop
Release v1.9.5
2017-04-06 16:35:15 -04:00
Jeremy Stretch
35a0a658a7 Release v1.9.5 2017-04-06 16:34:00 -04:00
Jeremy Stretch
2c99a8bee4 Closes #1052: Added rack reservation list and bulk delete views 2017-04-06 16:26:48 -04:00
Jeremy Stretch
1dd2bdcb8e Fixes #1047: Correct ordering of numbered subinterfaces 2017-04-06 15:13:20 -04:00
Jeremy Stretch
9f67da00d1 Colored nodes in topology maps 2017-04-06 14:12:30 -04:00
Jeremy Stretch
82d53a8c3d Fixes #1049: Prompt user if missing session key when adding/editing a secret 2017-04-06 13:55:40 -04:00
Jeremy Stretch
f3eee25527 Fixes #1051: Upgraded django-rest-swagger 2017-04-06 11:54:13 -04:00
Jeremy Stretch
ee11775425 Fixes #1051: Upgraded django-rest-swagger 2017-04-06 09:40:09 -04:00
Jeremy Stretch
bcdf9ac5ca Merge pull request #1046 from digitalocean/component-filter-by-name
Fixes #1045
2017-04-06 09:14:41 -04:00
Jeremy Stretch
4accdf77f8 Closes #578: Show topology maps not assigned to a site on the home view 2017-04-05 17:33:39 -04:00
Jeremy Stretch
fc46f70153 Closes #430: Include circuits when rendering topology maps 2017-04-05 17:24:40 -04:00
Zach Moody
e7cf7d58b8 Fixes #1045 2017-04-05 15:29:53 -05:00
Jeremy Stretch
d98e9e1838 Resolved RemovedInDjango20Warning deprecation warnings 2017-04-05 14:40:25 -04:00
Jeremy Stretch
369d3aa62e Rearranged URL namespaces to satisfy deprecation warnings 2017-04-05 14:26:33 -04:00
Jeremy Stretch
d4ac6dbfe4 Fixes #1043: Corrected queryset in WritableDeviceSerializer validation 2017-04-05 13:38:23 -04:00
Jeremy Stretch
91d35905fd Reset version 2017-04-05 12:11:48 -04:00
Jeremy Stretch
1a34830f0e Merge pull request #1042 from digitalocean/v2-develop
Release v2.0 Beta 2
2017-04-05 11:57:04 -04:00
Jeremy Stretch
f000df1e15 Release v2.0-beta2 2017-04-05 11:52:14 -04:00
Jeremy Stretch
78b0072051 Limit <v2.0 installations to Django 1.10 2017-04-05 11:34:04 -04:00
Jeremy Stretch
178f7b4643 Added API endpoints for console and power connections 2017-04-05 11:29:12 -04:00
Jeremy Stretch
f34a8fff6a Extend upgrade script to remove cached bytecode 2017-04-05 10:22:37 -04:00
Jeremy Stretch
7766e1f684 Fixes #1037: Fixed error on VLAN import with duplicate VLAN group names 2017-04-05 10:13:19 -04:00
Jeremy Stretch
bde1f6d199 Bump migration index due to a new migration in 1.9.4-r1 2017-04-04 15:59:33 -04:00
Jeremy Stretch
0d7ee6f208 Merge branch 'develop' into v2-develop 2017-04-04 15:56:33 -04:00
Jeremy Stretch
78adaecb89 Post-release version bump 2017-04-04 15:50:59 -04:00
Jeremy Stretch
f89d91783b Merge pull request #1035 from digitalocean/develop
Release v1.9.4-r1
2017-04-04 15:50:28 -04:00
Jeremy Stretch
a18e1a0161 Release v1.9.4-r1 2017-04-04 15:47:25 -04:00
Jeremy Stretch
4308b8a4a5 Fixes #1034: Missing migration 2017-04-04 15:46:27 -04:00
Jeremy Stretch
280d98bad9 #1033: Updated requirements.txt for Django 1.11 2017-04-04 14:42:00 -04:00
Jeremy Stretch
ae5bf747c9 #1033: Tweak SelectWithDisabled option template 2017-04-04 14:36:35 -04:00
Jeremy Stretch
1ae0820ecc #1033: Update ArrayFieldSelectMultiple for Django 1.11 2017-04-04 14:30:00 -04:00
Jeremy Stretch
c09473f41e #1033: Convert SelectWithDisabled to a templatized widget 2017-04-04 14:19:48 -04:00
Jeremy Stretch
99a3e0c399 Corrected CustomFieldModelSerializer behavior when serializing lists of objects 2017-04-04 14:09:14 -04:00
Jeremy Stretch
d2bd4a213b #1033: Convert ColorSelect to a templatized widget 2017-04-04 13:45:32 -04:00
Jeremy Stretch
1dcb0b52e2 #1033: Disable the debug toolbar templates panel by default due to a performance issue under Django 1.11 2017-04-04 13:44:55 -04:00
Jeremy Stretch
409c9c4e23 Merge branch 'develop' into v2-develop
Conflicts:
	netbox/netbox/settings.py
2017-04-04 12:06:49 -04:00
Jeremy Stretch
aa54e14c37 Post-release version bump 2017-04-04 12:03:26 -04:00
Jeremy Stretch
3ffe36e5ed Merge pull request #1032 from digitalocean/develop
Release v1.9.4
2017-04-04 12:01:58 -04:00
Jeremy Stretch
3b2c74042e Release v1.9.4 2017-04-04 11:58:44 -04:00
Jeremy Stretch
11ae938146 Fixes #1027: Fixed nav menu highlighting when BASE_PATH is set 2017-04-04 11:55:16 -04:00
Stephen
f11bb254a5 Only show Custom Fields on IP Address Assign Page if custom fields are set against the ip address (#1031) 2017-04-04 11:37:20 -04:00
Jeremy Stretch
5187138547 Refactored custom field serializers 2017-04-04 11:25:23 -04:00
Jeremy Stretch
2bb6387dae Fixes #1028: Corrected API endpoint URL name in IPAddressForm 2017-04-03 17:11:22 -04:00
Jeremy Stretch
ca293dc0e7 Corrected topology map link 2017-04-03 16:34:37 -04:00
Jeremy Stretch
ea1d4e7f50 Updated static CSS/JS libraries 2017-04-03 16:15:06 -04:00
Jeremy Stretch
0b681c471e Removed survey notice 2017-04-03 16:01:03 -04:00
Jeremy Stretch
80267aa418 Documented image attachments 2017-04-03 15:56:04 -04:00
Jeremy Stretch
36c31a21b9 Fixed deprecated references to ImageAttachment.obj 2017-04-03 15:51:45 -04:00
Jeremy Stretch
51725d3d9c Added a search box to the navigation menu 2017-04-03 15:33:41 -04:00
Jeremy Stretch
05d3354570 Fixes #1022: Record user actions when creating IP addresses in bulk 2017-04-03 14:45:20 -04:00
Jeremy Stretch
8799a15e73 What would we do without you, PEP8? 2017-04-03 14:26:20 -04:00
Jeremy Stretch
2cde9a82a0 Merge pull request #1026 from digitalocean/image-attachments
#152: Image attachments
2017-04-03 14:21:15 -04:00
Jeremy Stretch
2c1fa628a2 Implemented API endpoints for ImageAttachments 2017-04-03 14:00:15 -04:00
Jeremy Stretch
a67fc64afb Fixes #1025: Applied missing API view filters 2017-04-03 11:04:17 -04:00
Jeremy Stretch
6bbdc2bae1 Enable serving static media through Django 2017-03-31 15:51:17 -04:00
Jeremy Stretch
50d7fd776f Added image attachments to sites and devices 2017-03-31 15:19:44 -04:00
Jeremy Stretch
1c38f705a7 Fixes #1021: Corrected evaluation of API token expiration time 2017-03-31 11:13:37 -04:00
Jeremy Stretch
b643939cc4 Initial work on #152: Image attachments 2017-03-30 21:55:57 -04:00
Jeremy Stretch
3ed3e93b25 Appended a version flag to all CSS/JS references to invalidate browser cache after an upgrade 2017-03-30 15:40:00 -04:00
Jeremy Stretch
f6ea09e581 Removed duplicate 'Regions' section 2017-03-30 10:27:20 -04:00
Jeremy Stretch
998f89216e Updated the docstring for Device 2017-03-30 09:56:47 -04:00
Jeremy Stretch
aefc6ff7b4 Merge branch 'global-search' into v2-develop 2017-03-29 16:45:57 -04:00
Jeremy Stretch
66615f1a96 Prettied things up a bit 2017-03-29 16:45:25 -04:00
Jeremy Stretch
a5dc91c175 Introduced SearchTable for improved performance 2017-03-29 16:05:23 -04:00
Jeremy Stretch
d04436aa0a Search form improvements 2017-03-29 14:22:27 -04:00
Jeremy Stretch
6542b8b198 Base64 decoding tweaks 2017-03-29 13:39:59 -04:00
Jeremy Stretch
6813787fc7 Fixes #1013: Show edit/delete reservation buttons on rack view 2017-03-29 12:15:14 -04:00
Jeremy Stretch
afdb24610d Initial work on global search 2017-03-29 12:04:57 -04:00
Jeremy Stretch
58e4bf1cc3 Closes #973: Removed extraneous admin UI functions 2017-03-28 16:41:53 -04:00
Jeremy Stretch
28761fc960 Closes #362: Added per_page query parameter to control pagination page length 2017-03-28 15:57:50 -04:00
Jeremy Stretch
69e54ab410 Token admin form improvements 2017-03-28 12:19:08 -04:00
Jeremy Stretch
116ceb6f93 Added tests for get-session-key API endpoint 2017-03-28 11:30:38 -04:00
Jeremy Stretch
5d022a575a Closes #985: Added preserve_key to get-session-key endpoint 2017-03-28 11:13:13 -04:00
Jeremy Stretch
e8fd0f3531 Order interfaces naturally for Device A 2017-03-27 10:55:54 -04:00
Jeremy Stretch
8103c399d5 Fixes #991: Correct server error on "create and connect another" interface connection 2017-03-27 10:53:32 -04:00
Jeremy Stretch
cf3e7f90d6 Merge pull request #997 from digitalocean/livesearch-fix
Fixes #996
2017-03-27 09:38:48 -04:00
Zach Moody
22bfac746e fix remaining legacy api url paths. 2017-03-24 18:58:19 -05:00
Zach Moody
066a3b8b52 update api_url with new interfaces endpoint. 2017-03-24 18:42:23 -05:00
Zach Moody
48141c0693 Fixes #996 2017-03-24 17:38:06 -05:00
Jeremy Stretch
576e21eb65 Merge branch 'develop' into v2-develop
Conflicts:
	netbox/netbox/settings.py
2017-03-23 17:14:41 -04:00
Jeremy Stretch
a51f5edbc8 Post-release version bump 2017-03-23 16:29:42 -04:00
Jeremy Stretch
be393a9d10 Merge pull request #989 from digitalocean/develop
Release v1.9.3
2017-03-23 16:27:06 -04:00
Jeremy Stretch
ef59f38ec4 Release v1.9.3 2017-03-23 16:24:35 -04:00
Jeremy Stretch
47120fae01 Rack assignment is optional for devices 2017-03-23 15:36:24 -04:00
Jeremy Stretch
93a4327921 Merge branch 'api2' into v2-develop 2017-03-23 13:36:09 -04:00
Jeremy Stretch
0f2bbd7bfd Merge pull request #986 from alejojo/patch-1
Update Dockerfile
2017-03-23 10:28:10 -04:00
Jeremy Stretch
c0417c1989 Closes #972: Add ability to filter connections list by device name 2017-03-23 10:07:02 -04:00
Jeremy Stretch
fb6cfa45fd Merge pull request #974 from marc-us/develop
Filter on mac address on interface
2017-03-23 09:35:01 -04:00
Mark
b875cea10d Filter on mac address on interface via API 2017-03-23 12:57:35 +01:00
Alejandro
516372e5db Update Dockerfile
change branch MASTER > v2.0-beta1
2017-03-23 18:39:14 +09:00
Jeremy Stretch
0899a1052e Only attempt to process session key if user is authenticated 2017-03-22 17:43:29 -04:00
Jeremy Stretch
32bf17c076 Closes #978: Allow filtering device types by function and subdevice role 2017-03-22 17:29:47 -04:00
Jeremy Stretch
66a6a8f33c Closes #983: Include peer device names when listing circuits in device view 2017-03-22 16:58:56 -04:00
Jeremy Stretch
007fe6a030 Markdown fixes 2017-03-22 10:49:20 -04:00
Jeremy Stretch
f5f9491811 v2.0 Beta 1 release 2017-03-22 10:07:37 -04:00
Jeremy Stretch
04e09c0078 Merge branch 'develop' into api2
Conflicts:
	netbox/circuits/filters.py
2017-03-22 09:48:41 -04:00
Jeremy Stretch
05b71564d8 Closes #981: Allow filtering primary objects by a given set of IDs 2017-03-22 09:39:30 -04:00
Jeremy Stretch
1791a5bb11 Added has_primary_ip filter for Devices 2017-03-21 21:29:03 -04:00
Jeremy Stretch
3e6a99fc22 Allow editing of platform RPC client 2017-03-21 17:33:40 -04:00
Jeremy Stretch
a5419ecc5c RPC API fixes 2017-03-21 17:24:16 -04:00
Jeremy Stretch
a36b138efe Added API doc for working with secrets 2017-03-21 16:00:02 -04:00
Jeremy Stretch
6d30fdb83d Finished work on secrets views; removed path from cookie assignment 2017-03-21 15:30:36 -04:00
Jeremy Stretch
5c4741c5d4 Added section on pagination 2017-03-21 14:34:52 -04:00
Jeremy Stretch
93c748bd3c Merge branch 'develop' into api2 2017-03-21 14:10:53 -04:00
Jeremy Stretch
7ba6e320e7 Fixes #843: Implemented CORS headers for API 2017-03-21 13:53:07 -04:00
Jeremy Stretch
54468ab1a8 Include the API version in responses 2017-03-21 13:23:56 -04:00
Jeremy Stretch
01f5435f63 Tweak how we set the API version 2017-03-21 13:17:50 -04:00
Jeremy Stretch
22768ff6c6 Renamed Module to InventoryItem (prep for #824) 2017-03-21 12:54:08 -04:00
Jeremy Stretch
122526a9d0 Custom name for ConnectedDeviceViewSet 2017-03-20 21:54:01 -04:00
Jeremy Stretch
6cb36a6cee Fixed browsable API breadcrumbs 2017-03-20 21:50:10 -04:00
Jeremy Stretch
925afe0999 Added test case for ConnectedDeviceViewSet 2017-03-20 21:39:40 -04:00
Jeremy Stretch
f743410b4e Renamed rack-units API and added a test 2017-03-20 21:18:37 -04:00
Jeremy Stretch
4a2206ecb1 Removed custom renderers 2017-03-20 17:47:18 -04:00
Jeremy Stretch
ffde2c96c7 Fixed custom renderers to work with paginated data 2017-03-20 17:15:42 -04:00
Jeremy Stretch
2bd46230be Converted ChoiceFieldSerializer to display an object 2017-03-20 16:32:59 -04:00
Jeremy Stretch
b04fe21d65 Wrote API endpoints, tests for ExportTemplates 2017-03-20 16:21:10 -04:00
Jeremy Stretch
266f9cc370 Added API endpoint, tests for Graphs 2017-03-20 15:14:33 -04:00
Jeremy Stretch
1682de59df Added a footer link to the GitHub wiki 2017-03-20 14:05:26 -04:00
Jeremy Stretch
42fd14f5c0 Introduced HttpStatusMixin to provide more detail on HTTP response status test failures 2017-03-20 13:46:47 -04:00
Jeremy Stretch
1988c02b7f Enforce API versioning 2017-03-20 12:33:42 -04:00
Jeremy Stretch
517eaa8b80 Expanded API documentation 2017-03-20 12:18:18 -04:00
Jeremy Stretch
1f78462f58 Updated RackViewSet() to be compatible with paginated API 2017-03-20 10:38:09 -04:00
Jeremy Stretch
36bbcc8559 Fix API JS to read response.results for new API 2017-03-20 10:06:25 -04:00
Mark
f26253ec49 Filter on mac address on interface 2017-03-18 21:26:33 +01:00
Mark
f2dc287f14 Filter on mac address on interface 2017-03-18 21:21:49 +01:00
Mark
3fe3151af7 Filter on mac address on interface
Extension to be able filter on mac address via API
2017-03-18 21:10:36 +01:00
Jeremy Stretch
1c1fd8f210 Limit tests to one per major Python version 2017-03-17 21:43:46 -04:00
Jeremy Stretch
671d53877a Python3 fixes 2017-03-17 21:39:29 -04:00
Jeremy Stretch
97710a4576 Make CI happy 2017-03-17 17:39:56 -04:00
Jeremy Stretch
c08fae8bce Restore not-so-extraneous 'id' field to all WritableSerializers 2017-03-17 17:32:43 -04:00
Jeremy Stretch
f02dd2f439 Merge branch 'develop' into api2 2017-03-17 17:06:01 -04:00
Jeremy Stretch
e544f1fa1e Removed extraneous 'id' field from all WritableSerializers 2017-03-17 16:20:34 -04:00
Jeremy Stretch
130ff27f26 Wrote tests for secrets API 2017-03-17 16:01:57 -04:00
Jeremy Stretch
79a9ac3bc8 Assign RackReservation user from request context 2017-03-17 14:45:14 -04:00
Jeremy Stretch
c5308d51f4 Make RackReservation.rack editble for API compatability 2017-03-17 14:40:11 -04:00
Jeremy Stretch
a6f4de5817 Wrote tests for IPAM API 2017-03-17 14:36:59 -04:00
Jeremy Stretch
8825a03033 Removed unneeded services endpoint from DCIM API 2017-03-17 12:23:23 -04:00
Jeremy Stretch
abdfc5c597 Finished DCIM API model tests 2017-03-17 12:16:24 -04:00
Jeremy Stretch
3ce2f0d100 Fix error when assigning a new interface to a LAG 2017-03-16 22:27:01 -04:00
Jeremy Stretch
be2faaa110 Fixed bug interpreting facility_id as a required field 2017-03-16 17:25:34 -04:00
Jeremy Stretch
f33269e50b First batch of DCIM API tests 2017-03-16 16:50:18 -04:00
Jeremy Stretch
bbc355df07 Improved create/update validation 2017-03-16 14:17:14 -04:00
Jeremy Stretch
d58f9031d1 Wrote tests for tenancy API 2017-03-16 13:29:55 -04:00
Jeremy Stretch
0312016f89 Wrote tests for circuits API 2017-03-16 13:23:01 -04:00
Jeremy Stretch
e3ae013e42 Implemented full read/write support for secrets 2017-03-15 14:47:18 -04:00
Jeremy Stretch
07a2b136b8 Refactored SecretViewSet 2017-03-15 13:48:09 -04:00
Jeremy Stretch
3d76a982aa Removed old API doc 2017-03-15 13:15:09 -04:00
Jeremy Stretch
92d726bbd4 Added examples to the graphs documentation 2017-03-15 12:16:46 -04:00
Jeremy Stretch
e2ef0bc3a6 Added survey announcement to README 2017-03-15 12:00:53 -04:00
Jeremy Stretch
13c29cb7a9 Post-release version bump 2017-03-14 17:18:05 -04:00
Jeremy Stretch
27eefd8705 Merge pull request #966 from digitalocean/develop
Release v1.9.2
2017-03-14 17:14:19 -04:00
Jeremy Stretch
b22c6a0078 Release v1.9.2 2017-03-14 17:07:04 -04:00
Jeremy Stretch
f4784412de Fixes #964: Fix bug when bulk editing/deleting filtered set of objects 2017-03-14 15:22:08 -04:00
Jeremy Stretch
33c5ea1f4e Fixes #963: Fix bug in IPv6 address range expansion 2017-03-14 15:06:34 -04:00
Jeremy Stretch
3dc15068b9 Allow user to delete session key 2017-03-14 14:01:06 -04:00
Jeremy Stretch
4cb30f1ce4 Relate SessionKey to UserKey rather than User 2017-03-14 13:32:07 -04:00
Jeremy Stretch
b868de8d67 Updated user URLs 2017-03-14 12:59:10 -04:00
Jeremy Stretch
04aedcc056 Merge branch 'develop' into api2
Conflicts:
	netbox/templates/users/_user.html
	netbox/users/urls.py
2017-03-14 12:40:28 -04:00
Jeremy Stretch
d9f1bcbf15 Renamed user URL namespace 2017-03-14 12:36:44 -04:00
Jeremy Stretch
105d17748e Secrets UI work 2017-03-14 12:32:08 -04:00
Jeremy Stretch
dd27950fae Simplify SessionKey usage 2017-03-14 10:58:57 -04:00
Jeremy Stretch
4b7af8d3b4 Merge pull request #954 from psr/develop
Force Unix line endings on shell scripts
2017-03-13 11:35:05 -04:00
Jeremy Stretch
f3fd82a24a Allow assigning child devices to rackless parents 2017-03-13 11:31:28 -04:00
Jeremy Stretch
cd97b2fb96 Fix parent device position display 2017-03-13 11:25:06 -04:00
Jeremy Stretch
f661c233be Fixes #950: Fix site_id error on child device import 2017-03-13 11:18:33 -04:00
Jeremy Stretch
6a2a2d5d11 Fixes #957: Correct device site filter count to include unracked devices 2017-03-13 10:13:04 -04:00
Jeremy Stretch
87ff433ef8 Fixes #956: Correct bug affecting unnamed rackless devices 2017-03-13 10:06:32 -04:00
Jeremy Stretch
9e4e3a8dfa Updated API docs 2017-03-13 10:00:13 -04:00
Jeremy Stretch
4d4441217f APIRootView tweaks 2017-03-09 15:18:50 -05:00
Jeremy Stretch
7e51ca9912 Provided a root API view 2017-03-09 15:05:01 -05:00
Jeremy Stretch
94a29be415 Removed deprecated GraphListView 2017-03-09 14:28:52 -05:00
Jeremy Stretch
9dfda83946 Closes #855: Added an API endpoint for recent activity 2017-03-09 14:26:39 -05:00
Jeremy Stretch
41826fc3cb Fixed serialization of CustomFieldChoices 2017-03-09 13:50:30 -05:00
Jeremy Stretch
0ed13f6943 Removed browsable API login/logout 2017-03-09 13:38:15 -05:00
Jeremy Stretch
6c2ed1be22 Standardized API URL definitions 2017-03-09 13:24:02 -05:00
Jeremy Stretch
ddec424429 Replaced RelatedConnectionsView with views.ConnectedDeviceViewSet 2017-03-09 12:18:53 -05:00
Peter Russell
d68b34cefe Force Unix line endings on shell scripts
When following the quickstart docker instructions on Windows (using
Docker for Windows), an error is encountered when running the netbox
container. This is caused by git converting the line endings of the
docker-entrypoint.sh script to Windows format, which are then copied
into the container image.

By setting .gitattributes, we force LF rather than CRLF line endings on
shell scripts on Windows. Other files are left as is.
2017-03-09 16:20:32 +00:00
Jeremy Stretch
7e6d061646 Converted GetSessionKey and RSAKeyGeneratorView to ViewSets 2017-03-08 17:57:51 -05:00
Jeremy Stretch
c19725506d Cleanup 2017-03-08 16:30:32 -05:00
Jeremy Stretch
a6ceaf8d96 Moved custom field serializers to their own module to avoid circular dependency 2017-03-08 16:18:41 -05:00
Jeremy Stretch
f43fbffdf7 Moved TopologyMaps from DCIM to extras 2017-03-08 16:12:14 -05:00
Jeremy Stretch
68c099a2af Merge branch 'develop' into api2
Conflicts:
	netbox/netbox/settings.py
2017-03-08 15:18:32 -05:00
Jeremy Stretch
70a05b4280 Post-release version bump 2017-03-08 14:45:23 -05:00
Jeremy Stretch
097e0f38ff Merge pull request #949 from digitalocean/develop
Release v1.9.1
2017-03-08 14:40:16 -05:00
Jeremy Stretch
094974d417 Release v1.9.1 2017-03-08 14:38:24 -05:00
Jeremy Stretch
d89314a559 Fixes #943: Child prefixes missing on Python 3 2017-03-08 14:10:27 -05:00
Jeremy Stretch
086340540a Fixes #948: Region name should be hyperlinked to site list 2017-03-08 12:04:50 -05:00
Jeremy Stretch
ed83b1d9e9 Closes #946: Disregard mask length when filtering IP addresses by a parent prefix 2017-03-08 11:59:24 -05:00
Jeremy Stretch
4f6d2a8b71 Finished user control panel for tokens 2017-03-08 11:34:47 -05:00
Jeremy Stretch
d58a8ebba0 Initial work on user control panel for tokens 2017-03-07 23:30:53 -05:00
Jeremy Stretch
6be465fe9b Addded is_expired property to Token 2017-03-07 23:30:31 -05:00
Jeremy Stretch
26225aff57 Shorten key length to 20 bytes 2017-03-07 22:56:29 -05:00
Jeremy Stretch
fd55360672 Suppress default permissions for Token model 2017-03-07 22:40:05 -05:00
Jeremy Stretch
4e766c7c3b Closes #945: Display the current user in the nav menu 2017-03-07 22:27:46 -05:00
Jeremy Stretch
0b10d98e0b Initial work on token authentication 2017-03-07 17:17:39 -05:00
Jeremy Stretch
be0a3fb1f2 Corrected merge conflict 2017-03-07 16:55:49 -05:00
Jeremy Stretch
02e89d77bb Merge branch 'develop' into api2
Conflicts:
	netbox/dcim/api/views.py
2017-03-07 14:08:06 -05:00
Jeremy Stretch
4b753b1610 Fixes #944: Correct console and power connection form behavior 2017-03-07 13:32:48 -05:00
Jeremy Stretch
f8381628d4 Fixes #941: Corrected old references to rack.site on Device 2017-03-06 10:23:24 -05:00
Jeremy Stretch
d8f41f67c9 Post-release version bump 2017-03-03 11:28:27 -05:00
Jeremy Stretch
ce26b566a4 Merge pull request #939 from digitalocean/develop
Release v1.9.0-r1
2017-03-03 11:28:02 -05:00
Jeremy Stretch
ee2d0b963d Release v1.9.0-r1 2017-03-03 11:26:31 -05:00
Jeremy Stretch
f051c0e564 Fixes #935: Fix form validation error when connecting an interface using live search 2017-03-03 11:19:19 -05:00
Jeremy Stretch
03d3bbcddb Fixes #938: Provider view yields an error if one or more circuits is assigned to a tenant 2017-03-03 10:47:16 -05:00
Jeremy Stretch
06cafb09b3 Fixes #937: Region assignment should be optional when creating a site 2017-03-03 10:44:07 -05:00
Jeremy Stretch
a7a7b956b1 Enable API versioning 2017-03-02 16:20:16 -05:00
Jeremy Stretch
9b39ba169c Merge branch 'develop' into api2
Conflicts:
	netbox/dcim/api/serializers.py
	netbox/dcim/api/urls.py
	netbox/dcim/api/views.py
	netbox/dcim/filters.py
	netbox/dcim/tables.py
	requirements.txt
2017-03-02 16:01:25 -05:00
Jeremy Stretch
4670929953 Added GitHub issue/PR templates; updated CONTRIBUTING policy 2017-03-02 15:30:14 -05:00
Jeremy Stretch
366e2e7a94 Post-release version bump 2017-03-02 13:30:45 -05:00
Jeremy Stretch
0e14bc1e02 Merge pull request #933 from digitalocean/develop
Release v1.9.0
2017-03-02 13:27:10 -05:00
Jeremy Stretch
e5f05ca9be Release v1.9.0 2017-03-02 13:22:29 -05:00
Jeremy Stretch
d08522408a Documentation refresh 2017-03-02 13:21:56 -05:00
Jeremy Stretch
cc31c8fc33 Closes #894: Expand device name max length to 64 characters 2017-03-02 10:11:54 -05:00
Jeremy Stretch
db60e8868c Closes #901: Support for filtering prefixes and IP addresses by mask length 2017-03-01 14:23:52 -05:00
Jeremy Stretch
1adae67dd7 Closes #927: Upgrade to django-filter 1.0 2017-03-01 13:09:19 -05:00
Jeremy Stretch
5ad3044314 Closes #862: Show both IPv6 and IPv4 primary IPs in device list 2017-02-28 16:46:44 -05:00
Jeremy Stretch
90fe556e5f Corrected region serializers 2017-02-28 16:23:39 -05:00
Jeremy Stretch
c0152940f9 Merged develop 2017-02-28 16:10:53 -05:00
Jeremy Stretch
9313ba08ed Implemented recursive regions with django-mptt 2017-02-28 14:15:15 -05:00
Jeremy Stretch
f3b9930dea Initial work on regions 2017-02-28 12:11:43 -05:00
Jeremy Stretch
5520144ff4 Fixed permissions for inclusion of interface bulk edit form 2017-02-27 17:19:30 -05:00
Jeremy Stretch
2b9ea58c86 Tweaked interface LAG validation 2017-02-27 17:13:58 -05:00
Jeremy Stretch
8f42f59a80 Merge branch 'develop' into api2
Conflicts:
	netbox/dcim/api/serializers.py
	netbox/dcim/api/views.py
	netbox/dcim/filters.py
2017-02-27 17:04:08 -05:00
Jeremy Stretch
c6970e1998 Closes #105: Interface groups (#919)
* Initial work on interface groups

* Simplify to a single LAG form factor

* Correct interface serializer

* Allow for bulk editing of interface LAG

* Additional LAG interface validation

* Fixed API tests
2017-02-27 16:52:13 -05:00
Jeremy Stretch
c61bae3a33 Merge pull request #908 from digitalocean/global-vlans
Closes #235: Global vlans
2017-02-21 14:58:52 -05:00
Jeremy Stretch
b0f9035e2d Additional work on #904 2017-02-21 14:53:22 -05:00
Shawn Peng
aba9748ffd Fix #235: Enable global vlan (#904)
* Fix #235: Enable global vlan

Decouple site/vlan, make site optional for vlan/vlangroup
Change html generation code to check site existence before
dereference
Create site search function, if site is None for a VLAN, view it as
global VLAN

* commit1

* commit2

* commit3

* Add migration file for VLAN&VLAN group

* Revert unintentional commits
2017-02-21 13:27:24 -05:00
Jeremy Stretch
2876ef7607 Merge pull request #907 from Jasperswaagman/develop
Typo
2017-02-21 09:24:17 -05:00
Jasperswaagman
7d1aeede1a Typo 2017-02-21 15:20:42 +01:00
Jeremy Stretch
b7f4a11eee Fixes #892: Restored missing edit/delete buttons when viewing child prefixes and IP addresses from a parent object 2017-02-17 16:34:09 -05:00
Jeremy Stretch
0e5138d6ec Fixes #872: TypeError on bulk IP address creation (Python 3) 2017-02-17 16:10:07 -05:00
Jeremy Stretch
f1518226bd Merge branch 'develop' into api2
Conflicts:
	netbox/dcim/api/serializers.py
2017-02-17 15:12:53 -05:00
Jeremy Stretch
4d26fc7e7c Fixes #903: Only alert on missing criticial connections if present in the parent device type 2017-02-17 15:10:08 -05:00
Jeremy Stretch
102cf52a16 Cleanup from work on #198 2017-02-17 14:52:58 -05:00
Jeremy Stretch
198ed859ff Closes #198: Support for rackless devices (#902)
* Initial work to support rackless devices

* Updated device component connection forms

* Updated IP address assignment form

* Updated circuit termination form

* Formatting cleanup

* Fixed tests
2017-02-17 14:48:00 -05:00
Jeremy Stretch
9d44d5d4e7 Fixes #897: Fixed power connections CSV export 2017-02-16 15:17:13 -05:00
Jeremy Stretch
e8896fe238 Closes #898: Expand circuits list in provider view 2017-02-16 15:13:35 -05:00
Jeremy Stretch
21281789e0 Tweaked ChoiceFieldSerializer to display a field as (value, label) 2017-02-16 14:37:21 -05:00
Jeremy Stretch
b71566f206 Merge branch 'develop' into api2
Conflicts:
	netbox/dcim/api/serializers.py
	netbox/dcim/api/urls.py
	netbox/dcim/api/views.py
	netbox/dcim/filters.py
2017-02-16 14:28:06 -05:00
Jeremy Stretch
181539651f Rack reservations (#900)
* Initial work on rack reservations

* Added views for rack reservations

* Implemented ArrayFieldSelectMultiple form widget

* Implemented API endpoints for rack reservations

* Tweaked the database migration
2017-02-16 13:46:58 -05:00
Jeremy Stretch
b69564f5c9 Exposed the request to ObjectEditView's alter_obj() 2017-02-15 15:03:31 -05:00
Jeremy Stretch
c26e00b5bd Merge pull request #850 from mryauch/patch-1
Update upgrading.md
2017-02-14 15:45:06 -05:00
Jeremy Stretch
dc606645fd Fixes #884: Preserve selected rack unit when changing a device's rack face 2017-02-09 23:27:12 -05:00
Jeremy Stretch
0e04d20762 Re-implemented CustomFieldSerializer (read-only for now) 2017-02-09 16:55:54 -05:00
Jeremy Stretch
7040086201 Introduced ChoiceFieldSerializer for choice fields 2017-02-09 15:50:25 -05:00
Jeremy Stretch
6f3c3b6d61 Added API endpoints for device type components 2017-02-03 17:18:47 -05:00
Jeremy Stretch
37f250ddc1 Corrected API URL names 2017-02-03 16:54:13 -05:00
Jeremy Stretch
35f310885e Standardize API URL inclusions 2017-02-03 16:20:14 -05:00
Jeremy Stretch
616ca4fe1f Adapted the web UI to work with the new secrets API 2017-02-03 16:14:42 -05:00
Jeremy Stretch
a9fe39459a Merge branch 'develop' into api2 2017-02-03 14:45:37 -05:00
Jeremy Stretch
1e1dd8c668 Post-release version bump 2017-02-03 13:59:26 -05:00
Jeremy Stretch
ce6796ed9b Merge pull request #870 from digitalocean/develop
Release v1.8.4
2017-02-03 13:59:02 -05:00
Jeremy Stretch
585e08eb95 Release v1.8.4 2017-02-03 13:55:32 -05:00
Jeremy Stretch
a42eeb12d2 Implemented SessionKeys for secrets 2017-02-03 12:49:32 -05:00
Jeremy Stretch
cf66f67fb6 Initial work on using session-based master key ciphers 2017-02-02 21:26:51 -05:00
Jeremy Stretch
2408d78f47 Introduced ability to decrypt secrets by sending the user's private key in an HTTP header 2017-02-01 17:40:50 -05:00
Jeremy Stretch
4f8a5eb1a0 Moved secret views into a ViewSet (no write ability yet) 2017-02-01 16:21:33 -05:00
Jeremy Stretch
06e5966cb4 Include API routers directly where possible 2017-02-01 15:09:23 -05:00
Jeremy Stretch
ea51f1c896 Removed circuit-specific endpoint for CircuitTerminations 2017-02-01 15:01:56 -05:00
Jeremy Stretch
77e5450746 Removed all device-specific API endpoints 2017-02-01 14:34:19 -05:00
Jeremy Stretch
6e10fea119 Started API documentation 2017-02-01 14:04:45 -05:00
Jeremy Stretch
f52c247bd5 Re-implemented Swagger now that URL resolution has been fixed 2017-02-01 12:37:19 -05:00
Jeremy Stretch
0dd857f7a2 Merge branch 'develop' into api2 2017-02-01 12:33:37 -05:00
Jeremy Stretch
d817990283 Fixes #865: Fix server error when attempting to delete a protected object parent (Python 3) 2017-02-01 12:09:59 -05:00
Jeremy Stretch
9905099a71 Fixes #854: Check whether object still exists before attempting to resolve its URL 2017-02-01 11:59:47 -05:00
Jeremy Stretch
0eba5a0de3 Fixes #851: Resolve encoding issues during import/export with Python 3 2017-02-01 11:49:54 -05:00
Jeremy Stretch
5eb3c1a67b Removed deprecated base_path Swagger setting 2017-02-01 10:48:36 -05:00
Jeremy Stretch
b370375414 Fixes #861: Avoid overwriting device primary IP assignment from alternate family during bulk import of IP addresses 2017-01-31 17:25:44 -05:00
Jeremy Stretch
8536f6c163 Closes #856: Strip whitespace from fields during CSV import 2017-01-31 16:54:13 -05:00
Jeremy Stretch
bb1f97abc2 Implemented static writable ModelSerializers for all models 2017-01-31 15:35:09 -05:00
Jeremy Stretch
e1cd846c9a Enabled creation of device components 2017-01-31 12:19:41 -05:00
Jeremy Stretch
1fcc2b0029 Namespaced all API URLs 2017-01-31 10:40:53 -05:00
Jeremy Stretch
f4f41a5985 Fixes #859: Fix Javascript for connection status toggle button 2017-01-31 09:41:25 -05:00
Jeremy Stretch
173a6eee03 Moved rack units and device LLDP neighbors views into model viewsets 2017-01-30 17:24:04 -05:00
Jeremy Stretch
d9e4017677 Moved graph views into model viewsets 2017-01-30 17:00:58 -05:00
Jeremy Stretch
7beac0b105 Converted device component views to a router 2017-01-30 16:15:12 -05:00
Jeremy Stretch
f0fef94a4f Re-implemented interface/connection serializers 2017-01-30 15:35:01 -05:00
Jeremy Stretch
78cd4481e4 Merge branch 'develop' into api2 2017-01-30 13:38:49 -05:00
Jeremy Stretch
af3c9eaec1 Fixes #854: Correct processing of get_return_url() in ObjectDeleteView 2017-01-30 12:13:24 -05:00
Jeremy Stretch
0cf029edd4 Added Service serializers 2017-01-27 16:19:38 -05:00
Jeremy Stretch
c0dac1383d Fix retrieval of model under viewsets without a statically defined queryset 2017-01-27 15:12:46 -05:00
Jeremy Stretch
a3d0d4a5bf Enabled pagination 2017-01-27 14:54:12 -05:00
Jeremy Stretch
12d263999b Introduced WritableSerializerMixin 2017-01-27 14:36:13 -05:00
Jeremy Stretch
fa900d5dbb Converted nested serializers to HyperlinkedModelSerializer 2017-01-27 12:22:29 -05:00
Jeremy Stretch
ddc2c8d110 Cleaned up device component nested serializers 2017-01-26 22:37:17 -05:00
Matthew Yauch
c91f41e984 Update upgrading.md
Added directive to copy the LDAP configuration if in use.  I upgraded a whole two versions before realizing my LDAP was broken.
2017-01-26 15:28:11 -08:00
Jeremy Stretch
acfba410dd Standardized implementation of nested ViewSets 2017-01-26 17:58:36 -05:00
Jeremy Stretch
b8ca530c55 Added an endpoint for CircuitTerminations 2017-01-26 17:18:41 -05:00
Jeremy Stretch
b31c097531 Removed Swagger 2017-01-26 15:36:19 -05:00
Jeremy Stretch
0f9fe8648e Converted static URL definitions to routers 2017-01-26 15:34:07 -05:00
Jeremy Stretch
791a641eef Created CircuitDetailSerializer 2017-01-26 15:33:41 -05:00
Jeremy Stretch
c5fba24cc5 Merge branch 'develop' into api2 2017-01-26 14:07:23 -05:00
Jeremy Stretch
b8b2ea7ccb Post-release version bump 2017-01-26 14:00:08 -05:00
Jeremy Stretch
c90cecc2fb Merge pull request #849 from digitalocean/develop
Release v1.8.3
2017-01-26 13:58:52 -05:00
Jeremy Stretch
b2ef7bb104 Release v1.8.3 2017-01-26 13:57:00 -05:00
Jeremy Stretch
5d5d4ac714 Fixes #845: Fix missing edit/delete buttons on object tables for non-superusers 2017-01-26 13:20:56 -05:00
Jeremy Stretch
0b228ed6d3 Merge branch 'develop' into api2 2017-01-25 16:26:45 -05:00
dav3860
b3b96e5e10 Support for comma in interfaces and ip addresses bulk creation (#833)
* Added support for comma in interfaces and ip addresses bulk creation

* fixed PEP8 style

* removed unnecessary assertions
2017-01-25 14:47:14 -05:00
Jeremy Stretch
6be520a8f9 Fixed DeviceTypeTest 2017-01-25 14:38:45 -05:00
Jeremy Stretch
f3db914e9d Fixes #844: Apply order_naturally() to API interfaces list 2017-01-25 14:34:34 -05:00
Jeremy Stretch
062a5bfe8d Initial work on API v2.0 2017-01-24 17:12:16 -05:00
Jeremy Stretch
fbfa3cf619 Added gunicorn_config.py to .gitignore 2017-01-24 13:41:46 -05:00
Jeremy Stretch
1317c0dd8c Closes #841: Merged search and filter forms on all object lists 2017-01-24 12:05:39 -05:00
Jeremy Stretch
bbc633b004 Closes #782: Allow filtering devices list by manufacturer 2017-01-24 10:53:59 -05:00
Jeremy Stretch
ed8fdd9292 Fixes #816: Redirect back to parent prefix view after deleting child prefixes 2017-01-24 09:50:51 -05:00
Jeremy Stretch
2d9c33c34f Tweaked installation docs to include instructions for Python 2 and 3 2017-01-23 17:01:23 -05:00
Jens L
80439c495e 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
2017-01-23 16:44:29 -05:00
Jeremy Stretch
1bddd038fe Fixes #840: Correct API path resolution for secrets when BASE_PATH is configured 2017-01-23 16:25:05 -05:00
Jeremy Stretch
d36923e47d Fixes #817: Update last_updated time of a circuit when editing a child termination 2017-01-23 15:31:41 -05:00
Jeremy Stretch
476cbf17f6 Closes #820: Add VLAN column to parent prefixes table on IP address view 2017-01-23 14:23:42 -05:00
Jeremy Stretch
91d50b9627 Closes #836: Add 'deprecated' status for IP addresses 2017-01-23 14:12:43 -05:00
Jeremy Stretch
52420945b2 Standardized naming of return_url for all object views 2017-01-23 14:07:26 -05:00
Jeremy Stretch
b70eca7661 Fixes #830: Redirect user to device view after editing a device component 2017-01-23 12:14:12 -05:00
Jeremy Stretch
39d083eae7 Re-implemented method for bulk editing/deleting all objects within a filtered queryset 2017-01-20 16:42:11 -05:00
Jeremy Stretch
3bfc1ebcea Post-release version bump 2017-01-18 16:23:52 -05:00
Jeremy Stretch
b6bbcb0609 Merge pull request #814 from digitalocean/develop
Release v1.8.2
2017-01-18 16:23:28 -05:00
Jeremy Stretch
6121f97ca9 Release v1.8.2 2017-01-18 16:19:45 -05:00
Jeremy Stretch
74e48fc490 PEP8 fixes 2017-01-18 14:43:46 -05:00
Jeremy Stretch
28a9307f9f Deprecated use_obj_view in favor of get_return_url() 2017-01-18 14:34:17 -05:00
Jeremy Stretch
cdccc3a47f Ditched get_parent_url() model method in favor of overrideable get_return_url() view method 2017-01-18 14:07:46 -05:00
Jeremy Stretch
3eb969de0c Standardized the use of return_url for ObjectDeleteView 2017-01-18 13:30:19 -05:00
Jeremy Stretch
9ff59ab686 Closes #760: Redirect user back to device view after deleting an assigned IP address 2017-01-18 12:25:07 -05:00
Jeremy Stretch
fc7f88d2a2 Regression fix: order_naturally() must come first in the queryset definition 2017-01-18 11:55:48 -05:00
Jeremy Stretch
769537fe98 Fixes #810: Suppress unique IP validation on invalid IP addresses and prefixes 2017-01-18 09:55:57 -05:00
Jeremy Stretch
f8a4f1b24f Closes #797: Add description column to VLANs table 2017-01-17 16:06:19 -05:00
Jeremy Stretch
7f3b358571 Fixes #807: Redirect user back to form when adding IP addresses in bulk and "create and add another" is clicked 2017-01-17 15:46:43 -05:00
Jeremy Stretch
c264281530 Add an empty label (global) to IPAddressBulkAddForm VRF field 2017-01-17 15:33:55 -05:00
Jeremy Stretch
b3f20aa233 Closes #783: Add a description field to the Circuit model 2017-01-17 15:18:03 -05:00
Jeremy Stretch
07997b24ca Fixes #785: Trigger validation error when importing a prefix assigned to a nonexistent VLAN 2017-01-17 15:01:30 -05:00
Jeremy Stretch
03859d7287 Closes #803: Clarify that no child objects are deleted when deleting a prefix 2017-01-17 14:52:39 -05:00
Jeremy Stretch
0ad2670822 Closes #805: Linkify site column in device table 2017-01-17 14:46:29 -05:00
Jeremy Stretch
ab706d2440 Follow-up to #804 2017-01-17 12:32:54 -05:00
Jeremy Stretch
398faf518c Merge pull request #804 from digitalocean/prefix-unique
Enforce Global Unique on Prefixes
2017-01-17 10:28:00 -05:00
Zach Moody
edf29e7b9b moved duplicates() method to model instead of manager. 2017-01-16 18:14:34 -06:00
Zach Moody
485a21f13e cleaned up IPAddress clean() to be more like Prefix's 2017-01-16 16:52:03 -06:00
Zach Moody
eedec192ba Added model tests for duplicate prefix and IPs. 2017-01-16 16:40:06 -06:00
Zach Moody
cfaf8b9157 added duplicates() method to IPAddress and Prefix model managers.
refactored condition on IPAddress and Prefix clean method to use new
manager method.
2017-01-16 16:28:04 -06:00
Jeremy Stretch
98e2145b52 Merge pull request #796 from rburkholder/rburkholder-patch-TenantSerializer-description
rburkholder - patch - tenant serialiser - add description field to output
2017-01-13 09:36:33 -05:00
Jeremy Stretch
466c505bb8 Corrected PEP8 errors 2017-01-13 09:30:59 -05:00
Raymond P. Burkholder
97c0f23c67 Add description field to TenantSerializer
This might be just an oversight.  Other data models do include the description in their serialisers.  The API produces the description field with this change.
2017-01-13 08:49:43 -04:00
Jeremy Stretch
424c2a59d6 Table rendering optimizations 2017-01-06 16:50:57 -05:00
Jeremy Stretch
c9e7c12463 Closes #284: Added interface_ordering field to DeviceType 2017-01-06 12:59:49 -05:00
Jeremy Stretch
2ef1e623a3 Merge pull request #781 from zevlag/patch-1
Update CONTRIBUTING.md to replace Reddit with Google Groups
2017-01-05 21:55:16 -05:00
Josh Galvez
1486a8901a Update CONTRIBUTING.md
Change Reddit to Google Groups Mailing List
2017-01-05 16:02:05 -07:00
Jeremy Stretch
73ae87aa57 Updated circuits documentation 2017-01-05 17:06:30 -05:00
Jeremy Stretch
ac72e90dcc Fixes #778: Refactored order_interfaces() to fix InterfaceTemplate ordering within a table 2017-01-05 16:12:07 -05:00
Jeremy Stretch
dbf9840b26 Corrected permissions for device component form rendering 2017-01-05 15:37:15 -05:00
Jeremy Stretch
09fe328c3f Standardized template names 2017-01-05 15:31:41 -05:00
Jeremy Stretch
381eb664cf Added alternative installations section 2017-01-04 17:16:04 -05:00
Jeremy Stretch
23c6451524 Fixes #776: Prevent circuits from appearing twice while searching 2017-01-04 16:56:28 -05:00
Jeremy Stretch
99cd78cbbf Post-release version bump 2017-01-04 15:31:10 -05:00
Jeremy Stretch
23f6832d9c Merge pull request #774 from digitalocean/develop
Release v1.8.1
2017-01-04 15:30:54 -05:00
Jeremy Stretch
bce23ebdf5 Release v1.8.1 2017-01-04 15:28:53 -05:00
Jeremy Stretch
0d4b2a6e92 Fixes #772: Fix TypeError in API RackUnitListView when no device is excluded 2017-01-04 10:58:11 -05:00
Jeremy Stretch
52567c4ade Fixes #764: Encapsulate in double quotes values containing commas when exporting to CSV 2017-01-04 10:47:00 -05:00
Jeremy Stretch
8154ae3685 Closes #771: Don't automatically redirect user when only one object is returned in a list 2017-01-04 09:51:40 -05:00
Jeremy Stretch
7f297b4733 Fixes #769: Show default value for boolean custom fields 2017-01-04 09:47:26 -05:00
Jeremy Stretch
96451bfe9e Fixes #767: Fixes xconnect_id error when searching for ciruits 2017-01-03 17:00:43 -05:00
Jeremy Stretch
921b08d0c9 Allow null filtering of prefix VLAN 2017-01-03 16:57:42 -05:00
Jeremy Stretch
6eff95a2b1 Post-release version bump 2017-01-03 15:14:09 -05:00
Jeremy Stretch
88dace75a1 Merge pull request #766 from digitalocean/develop
Release v1.8.0
2017-01-03 15:13:36 -05:00
Jeremy Stretch
f8bced34eb Release v1.8.0 2017-01-03 15:07:41 -05:00
Jeremy Stretch
cf64ef342f Fixes #763: Added missing fields to CSV exports 2017-01-03 14:52:56 -05:00
Jeremy Stretch
c7acc9ad69 Updated circuit import template 2017-01-03 14:25:51 -05:00
Jeremy Stretch
31e8986e35 Minor docs tweaks 2017-01-03 14:03:50 -05:00
Jeremy Stretch
050b6449d4 Split site contact info into a separate panel 2017-01-03 13:46:49 -05:00
Jeremy Stretch
49dd5761f8 Tweaked web server installation docs 2017-01-03 13:44:22 -05:00
Jeremy Stretch
5215779061 Fixes #757: Debug toolbar middleware must always be included, even if DEBUG is False 2016-12-29 13:45:57 -05:00
Jeremy Stretch
48e9cd6a00 Miscellaneous cleanup and documentation 2016-12-29 13:42:38 -05:00
Jeremy Stretch
e06bfffd60 Fixed outdated select_related reference to circuit 2016-12-29 11:53:24 -05:00
Jeremy Stretch
e7b08f8f2f Closes #756: Added contact details to site model 2016-12-29 11:37:40 -05:00
Jeremy Stretch
8edaff860c Fixes #658: Added is_pool field to Prefix model 2016-12-27 15:07:52 -05:00
Jeremy Stretch
d9d7068c5f Fixed bug introduced in 04fd197c9b 2016-12-27 14:18:31 -05:00
Jeremy Stretch
e647065e63 Improved device interface list performance 2016-12-27 13:21:19 -05:00
Jeremy Stretch
5716207ba6 Simplified paginator when dealing with <=5 pages 2016-12-26 15:43:48 -05:00
Jeremy Stretch
bdff71db9e Fixed paginator text rendering 2016-12-26 15:36:33 -05:00
Jeremy Stretch
9e670d318c Relaxed version requirements 2016-12-26 15:33:08 -05:00
Jeremy Stretch
1882d832c3 Bumped django-tables2 version 2016-12-26 14:34:13 -05:00
Jeremy Stretch
04fd197c9b Fixed table form rendering for django-tables2>=1.2.1 2016-12-26 14:30:56 -05:00
Jeremy Stretch
edb8904474 Fixed debug toolbar display 2016-12-26 12:15:14 -05:00
Jeremy Stretch
a5fe4468d0 Upgraded django-filter to 0.15.3 2016-12-26 11:58:27 -05:00
Jeremy Stretch
65d8bb8c26 Bumped Markdown version 2016-12-26 11:05:46 -05:00
Jeremy Stretch
cf796fb40f Fixes #751: Relax version constraint on python-cryptography 2016-12-26 10:55:14 -05:00
Jeremy Stretch
0ac3e91e3b Updated middleware for Django 1.10 2016-12-26 10:48:15 -05:00
Jeremy Stretch
e8684240a7 Added permissions evaluation 2016-12-21 17:28:35 -05:00
Jeremy Stretch
c1b6da771f Only display "select all" button if there are two or more items 2016-12-21 17:26:25 -05:00
Jeremy Stretch
3de51876d0 Refactored device component creation views 2016-12-21 17:20:27 -05:00
Jeremy Stretch
0e4d02bd10 Renamed template 2016-12-21 15:31:55 -05:00
Jeremy Stretch
7b06f5e9fc Introduced DeviceComponentCreateView 2016-12-21 15:26:56 -05:00
Jeremy Stretch
37b2ff02e7 Standardized inheritance order of BootstrapMixin 2016-12-21 14:15:18 -05:00
Jeremy Stretch
1ed5389703 Fixed device component bulk creation permissions 2016-12-21 13:52:16 -05:00
Jeremy Stretch
b6da5ce6bd Fixed device type component creation permissions 2016-12-21 13:47:15 -05:00
Jeremy Stretch
ae8f40ed8d Fixes #563: Allow a device to be flipped from one rack face to the other 2016-12-21 11:06:42 -05:00
Jeremy Stretch
96de61ddfb Closes #716: Add ASN field to site bulk edit form 2016-12-20 16:13:45 -05:00
Jeremy Stretch
9fd9719d0b Closes #181: Implemented support for bulk IP address creation 2016-12-20 15:39:22 -05:00
Jeremy Stretch
f0d8e02d63 Fixed prefix/VLAN role links 2016-12-19 14:45:25 -05:00
Jeremy Stretch
44d5ff26a4 Fixes #747: Fixes natural_order_by integer cast error on large numbers 2016-12-19 11:01:44 -05:00
Jeremy Stretch
550efcb640 Tweaked prefix column header padding 2016-12-16 17:09:27 -05:00
Jeremy Stretch
15bec75167 Fixes #744: Fixed export of sites without an AS number 2016-12-16 16:33:07 -05:00
Jeremy Stretch
c94d111401 Closes #743: Enabled bulk creation of all device components 2016-12-16 16:29:32 -05:00
Jeremy Stretch
6f1532adac Fixed dcim tests 2016-12-16 12:15:32 -05:00
Jeremy Stretch
b7fe220860 Converted module_add to ObjectEditView 2016-12-16 12:12:42 -05:00
Jeremy Stretch
b451ece057 Closes #122: Add comments field to device types 2016-12-16 11:14:44 -05:00
Jeremy Stretch
b56e37ad84 Closes #722: Enabled custom fields for device types 2016-12-16 10:54:45 -05:00
Jeremy Stretch
712567cabc Closes #613: Added prefixes column to VLAN list; added VLAN column to prefix list 2016-12-15 16:56:43 -05:00
Jeremy Stretch
017263f640 Fixes #741: Hide "select all" button for users without edit permissions 2016-12-15 15:58:50 -05:00
Jeremy Stretch
f02c222d4f Closes #539: Implemented L4 services for devices 2016-12-15 15:32:58 -05:00
Jeremy Stretch
66fa877198 ObjectEditView: Save many-to-many fields 2016-12-15 14:37:35 -05:00
Jeremy Stretch
6a9f26a68d Cleaned up attribute tables 2016-12-14 17:09:33 -05:00
Jeremy Stretch
bf817eb69e Closes #49: Introduction of circuit terminations 2016-12-14 13:47:22 -05:00
Jeremy Stretch
298ac1ba7a Widened page layout; improved mobile rendering 2016-12-09 16:23:11 -05:00
Jeremy Stretch
bd40f72ad5 #49: Allow selection of devices at other sites when connecting an interface 2016-12-09 11:43:50 -05:00
Jeremy Stretch
a0eff04185 Post-release version bump 2016-12-08 12:35:18 -05:00
Jeremy Stretch
8eb140fd65 Merge pull request #736 from digitalocean/develop
Release v1.7.3
2016-12-08 12:34:53 -05:00
Jeremy Stretch
a68e82575f Release v1.7.3 2016-12-08 12:33:36 -05:00
Jeremy Stretch
5035a9567b Fixes #729: Corrected cancellation links when editing secondary objects 2016-12-08 12:20:45 -05:00
Jeremy Stretch
d5095362d7 Fixes #734: Corrected display of device type when editing a device 2016-12-08 09:59:21 -05:00
Jeremy Stretch
3a6d7a1f7f #733: Fixed MAC address device filter 2016-12-07 15:53:19 -05:00
Jeremy Stretch
cc6ae8ebe4 Merge pull request #733 from linuxsimba/remove_mac_addr_required_from_filter
FIX: filtering devices fails because mac address filter is a required
2016-12-07 15:42:50 -05:00
stanley karunditu
b4940a64be FIX: filtering devices fails because mac address filter is a required
field
2016-12-07 15:30:57 -05:00
Jeremy Stretch
fca812928e #724: Exempt API views from LoginRequiredMiddleware to enable basic HTTP authentication when LOGIN_REQUIRED is true 2016-12-07 15:14:22 -05:00
Jeremy Stretch
4a9b4c5387 Fixes #732: Allow custom select field values to be deselected if the field is not required 2016-12-07 14:00:52 -05:00
Jeremy Stretch
1f09f3d096 Merge pull request #728 from digitalocean/develop
Release v1.7.2-r1
2016-12-06 15:38:52 -05:00
Jeremy Stretch
efb95937fc Reverting GitHub test 2016-12-06 15:32:11 -05:00
Jeremy Stretch
ce7ee1771a Testing GitHub 2016-12-06 15:31:31 -05:00
Jeremy Stretch
da216e2c22 Fixes #727: Corrected error in rack elevation display 2016-12-06 15:27:35 -05:00
Jeremy Stretch
e58ee4e0e3 Post-release version bump 2016-12-06 14:55:45 -05:00
Jeremy Stretch
66be85a41f Merge pull request #726 from digitalocean/develop
Release v1.7.2
2016-12-06 14:55:19 -05:00
Jeremy Stretch
2171dcee7f Release v1.7.2 2016-12-06 14:53:43 -05:00
Jeremy Stretch
3262262a8a Closes #663: Added MAC address search field to device list 2016-12-06 14:45:01 -05:00
Jeremy Stretch
28b586aca7 Fixes #723: API documentation is now accessible when using BASE_PATH 2016-12-06 14:08:25 -05:00
Jeremy Stretch
f007b0dbde Closes #695: Added is_private field to RIR 2016-12-06 13:59:13 -05:00
Jeremy Stretch
6e5950be77 Fixes #720: Display user action links properly in admin UI 2016-12-06 13:16:42 -05:00
Jeremy Stretch
eb4cd0e723 Fixes #672: Expanded color selection for rack and device roles 2016-12-06 12:28:29 -05:00
Jeremy Stretch
300ee820fa #672: Cleaned up rack elevation CSS 2016-12-05 18:11:07 -05:00
Jeremy Stretch
7d6d7942d9 Rewrote get_connected_interface() to return first connection if more than one exist (erroneously) 2016-12-02 16:09:07 -05:00
Jeremy Stretch
05debf7e40 Updated CONTRIBUTING to discourage the use of "+1" comments on issues 2016-12-01 15:16:04 -05:00
Jeremy Stretch
dc88cb5ac7 Fixes #718: Restore is_primary field on IP assignment form 2016-12-01 14:54:20 -05:00
Jeremy Stretch
b275009544 Fixed missing on command block 2016-11-30 12:10:46 -05:00
Jeremy Stretch
d960481adb Ditched syntax highlighting for shell commands 2016-11-30 12:07:51 -05:00
Jeremy Stretch
2986840755 Specified syntax for code blocks 2016-11-30 12:01:45 -05:00
Jeremy Stretch
9b8bae501b Fixes #677: Add cffi as an explicit dependency to avoid setuptools error on Debian 2016-11-30 11:19:28 -05:00
Jeremy Stretch
9ea3383fde #702: Fix lingering Unicode incompatibility 2016-11-29 17:33:22 -05:00
Jeremy Stretch
77ac79f32c Fixes #713: Include a label for the comments field when editing circuits, providers, or racks in bulk 2016-11-29 17:29:56 -05:00
Jeremy Stretch
e31fae5ec5 Fixes #712: Corrected export of tenants which are not assigned to a group 2016-11-29 13:45:31 -05:00
Jeremy Stretch
8bff8bcbe2 Fixes #702: Improved Unicode support for custom fields 2016-11-29 13:34:22 -05:00
Jeremy Stretch
cc79b1136b Fixes #696: Corrected link to VRF in prefix and IP address breadcrumbs 2016-11-18 09:49:04 -05:00
Jeremy Stretch
1af9ea9e2d Post-release version bump 2016-11-15 12:36:17 -05:00
Jeremy Stretch
814c11167e Merge pull request #694 from digitalocean/develop
Release v1.7.1
2016-11-15 12:34:09 -05:00
Jeremy Stretch
1d509a8ff8 Release v1.7.1 2016-11-15 12:13:42 -05:00
Jeremy Stretch
f2232a15d9 Merge pull request #689 from bemanuel/develop
Add Graphviz to Topology Maps
2016-11-14 12:10:13 -05:00
Jeremy Stretch
955abcef21 Fixes #691: Allow the assignment of power ports to PDUs 2016-11-14 11:29:03 -05:00
Jeremy Stretch
9eaf153673 Fixes #692: Form errors are not displayed on checkbox fields 2016-11-14 11:13:27 -05:00
Bruno Emanuel
8e71c0f2a8 Removed python-graphviz 2016-11-13 13:29:11 -03:00
Jeremy Stretch
18a516ee53 Closes #685: When assigning an IP to a device, automaitcally select the interface if only one exists 2016-11-11 15:29:40 -05:00
Jeremy Stretch
f5b2420b4b Merge pull request #686 from digitalocean/rir_stats
#667: Add utilization statistics to RIR list view
2016-11-11 15:13:41 -05:00
Jeremy Stretch
f569561997 Another PEP8 fix 2016-11-11 15:09:25 -05:00
Jeremy Stretch
99c2911a66 PEP8 fix 2016-11-11 15:04:14 -05:00
Jeremy Stretch
a0ee6b0d58 Closes #667: Added stats to RIR list view 2016-11-11 15:02:53 -05:00
Jeremy Stretch
d891c8c981 Incorporated stats into RIR list view 2016-11-11 12:45:24 -05:00
Jeremy Stretch
07e34fbe84 Fixes #678: Server error on device import specifying an invalid device type 2016-11-10 15:30:45 -05:00
Jeremy Stretch
7dfd32a5c4 Fixes #676: Server error when bulk editing device types 2016-11-10 15:15:55 -05:00
Jeremy Stretch
9c7f55d8d0 Fixes #674: Correct status assignment on IP address import 2016-11-10 15:01:05 -05:00
Bruno Emanuel
e496dc710f Add Graphviz to Topology Maps 2016-11-04 18:01:07 -03:00
Jeremy Stretch
13cdc44caf #667: Initial work on RIR statistics 2016-11-04 16:04:29 -04:00
Jeremy Stretch
1f3f9781d9 Post-release version bump 2016-11-03 15:13:15 -04:00
Jeremy Stretch
57ddd5086f Merge pull request #666 from digitalocean/develop
Release v1.7.0
2016-11-03 15:12:33 -04:00
Jeremy Stretch
6c1fb1bd02 Release v1.7.0 2016-11-03 15:06:08 -04:00
Jeremy Stretch
ea92e92c5a Fixes #632: Use semicolons instead of commas to separate regexes in topology maps 2016-11-03 14:49:02 -04:00
Jeremy Stretch
96eaea7db9 Miscellaneous cleanup 2016-11-03 14:15:57 -04:00
Jeremy Stretch
bbac6e2ba6 Fixes #664: Re-implemented view for bulk creation of interfaces across multiple devices 2016-11-02 16:43:24 -04:00
Jeremy Stretch
76c6fbbfba Merge pull request #653 from flokli/gitignore
gitignore static folder, concretize configuration.py location
2016-11-01 15:44:49 -04:00
Jeremy Stretch
ad1c3d4910 Fixed typo in error message 2016-11-01 15:44:10 -04:00
Jeremy Stretch
f2137683f9 Closes #647: Extend form used when assigning an IP to a device 2016-11-01 13:59:24 -04:00
Jeremy Stretch
084b86cab1 Tweaked Aggregate get_utilization for table display 2016-10-31 16:47:00 -04:00
Jeremy Stretch
41af9c8900 Fixes #660: Correct calculation of utilized space for rack list display 2016-10-31 16:44:00 -04:00
Jeremy Stretch
2d58cfaa05 Add is_full_depth and instance count columns to DeviceType table 2016-10-31 15:29:32 -04:00
Jeremy Stretch
4af3072b53 Fix typo in c525939b13 2016-10-31 14:48:33 -04:00
Jeremy Stretch
a37d2ff4f8 Closes #652: Use password input controls when editing secrets 2016-10-31 12:30:50 -04:00
Jeremy Stretch
c525939b13 Closes #654: Added Cisco FlexStack and FlexStack Plus form factors 2016-10-31 12:22:05 -04:00
Jeremy Stretch
dc186a57cd Closes #661: Display relevant IP addressing when viewing a circuit 2016-10-31 11:36:05 -04:00
Jeremy Stretch
d97dd266b7 Cleaned up message strings 2016-10-31 11:16:30 -04:00
Jeremy Stretch
df9a6a0c53 Standardized device component deletion views to use ObjectDeleteView() 2016-10-28 17:00:41 -04:00
Jeremy Stretch
fd38daf0c5 Standardized device component edit views to use ObjectEditView() 2016-10-28 16:14:23 -04:00
Jeremy Stretch
28b4f6b8fd #181: Added ExpandableIPAddressField 2016-10-28 15:12:53 -04:00
Jeremy Stretch
2db50dd4a7 Closes #191: Support for racks numbered top-to-bottom 2016-10-28 11:30:40 -04:00
Florian Klink
5cd9c11169 gitignore static folder, concretize configuration.py location
this adds the netbox/static folder to the gitignore file, and further
specifies the path from where we'd like to ignore net netbox configuration.py.
2016-10-28 12:28:13 +02:00
Jeremy Stretch
f8f5d6876b Merge pull request #634 from jsenecal/patch-2
Fixed "Power Port" column name
2016-10-24 17:28:59 -04:00
Jonathan Senecal
198674f368 Fixed "Power Port" column name 2016-10-24 17:22:01 -04:00
Jeremy Stretch
e22eafc4a7 Closes #211: Allow device assignment and removal from IP address view 2016-10-24 15:07:11 -04:00
Jeremy Stretch
f44a322df5 Closes #630: Added a custom 404 page 2016-10-24 13:53:58 -04:00
Jeremy Stretch
fc2ac8a02b Attributed all model ValidationErrors to specific fields (where appropriate) 2016-10-21 15:39:13 -04:00
Jeremy Stretch
13243785f1 Closes #87: Added status field to IP addresses 2016-10-21 12:34:02 -04:00
Jeremy Stretch
35c207e936 Merge pull request #627 from kryskool/patch-1
Fix path to find configuration.py on migration
2016-10-20 13:44:20 -04:00
Christophe CHAUVET
998608111f Fix path to find configuration.py on migration 2016-10-20 09:55:03 +02:00
Jeremy Stretch
6018700421 Post-release version bump 2016-10-19 16:27:46 -04:00
Jeremy Stretch
c171547037 Merge pull request #625 from digitalocean/develop
Release v1.6.3
2016-10-19 16:25:50 -04:00
Jeremy Stretch
493b7d594d Release v1.6.3 2016-10-19 16:21:01 -04:00
Jeremy Stretch
4d40c015e4 Added instance count to DeviceType view 2016-10-19 15:56:15 -04:00
Jeremy Stretch
4405bc4182 Closes #608: Add "toggle all" button to device and device type components 2016-10-19 15:45:26 -04:00
Jeremy Stretch
54a0639a6e Merge pull request #623 from jsenecal/patch-1
Removed superfluous "is" in error message
2016-10-19 14:51:08 -04:00
Jonathan Senecal
334b286ebf Removed superfluous "is" in error message 2016-10-19 14:22:34 -04:00
Jeremy Stretch
c09cb5df3d #353: Added bulk editing for InterfaceTemplates 2016-10-19 12:15:54 -04:00
Jeremy Stretch
0da3661ff0 #353: Allow bulk editing of interfaces 2016-10-14 16:38:46 -04:00
Jeremy Stretch
5a4ccbc066 Fixes #616: Correct display of custom URL fields 2016-10-14 11:08:09 -04:00
Jeremy Stretch
49cbdc22da Fixes #615: Account for BASE_PATH in static URLs and during login 2016-10-13 16:27:09 -04:00
Jeremy Stretch
579ed0a985 Redirect user to previous page after logging in 2016-10-13 16:12:27 -04:00
Jeremy Stretch
464797858f Fixes #604: Correct display of unnamed devices in form selection fields 2016-10-13 15:21:36 -04:00
Jeremy Stretch
0ff46bf5d0 Fixes #611: Power/console/interface connection import: status field should be case-insensitive 2016-10-13 12:18:32 -04:00
Jeremy Stretch
330abe5a2d Fixes #602: Correct display of custom integer fields with value of 0 or 1 2016-10-05 15:29:16 -04:00
Jeremy Stretch
73945899fe Fixes #527: Support for nullifying custom fields during bulk editing 2016-10-05 15:17:17 -04:00
Jeremy Stretch
8227a9ff9c Merge pull request #592 from lf-/patch-2
Allow multiple ALLOWED_HOSTS on docker
2016-10-04 15:01:31 -04:00
Jeremy Stretch
f1c70cd896 Fixes #591: Correct display of device type component creation buttons 2016-10-04 14:46:27 -04:00
Jeremy Stretch
7055292803 Merge pull request #596 from digitalocean/bugfix-591
Fixes #591: Correct display of device type component creation buttons
2016-10-04 10:22:04 -04:00
Jeremy Stretch
3503c77699 Fixes #591: Correct display of component creation buttons in device type view 2016-10-04 10:17:56 -04:00
lf
b68c64041e Allow multiple ALLOWED_HOSTS on docker
Change ALLOWED_HOSTS to be a space delimited list.
2016-10-02 21:01:29 -06:00
Jeremy Stretch
36066068d4 #527: Initial work to allow nullifying fields during bulk edit 2016-09-30 16:17:41 -04:00
Jeremy Stretch
8ed174e7af Post-release version bump 2016-09-30 11:26:08 -04:00
Jeremy Stretch
7336fdf162 Merge pull request #587 from digitalocean/develop
Release v1.6.2
2016-09-30 11:24:16 -04:00
Jeremy Stretch
b5a7dd7d6d Reformatted URLs list to make pep8 happy 2016-09-30 11:19:50 -04:00
Jeremy Stretch
35918ae966 Release v1.6.2 2016-09-30 11:06:31 -04:00
Jeremy Stretch
ce01bb59a3 Fixed tests 2016-09-29 16:56:30 -04:00
Jeremy Stretch
18a5a966e3 Fixes #212: Tweak APISelect widget to inject BASE_PATH in API URL 2016-09-29 16:41:02 -04:00
Jeremy Stretch
833499ffe8 #212: Introduced BASE_PATH configuration setting 2016-09-29 16:32:16 -04:00
Jeremy Stretch
5b7f350ded Added headers to all bulk edit tables 2016-09-28 17:20:16 -04:00
Jeremy Stretch
d5fc0e9ce7 Closes #345: Bulk edit: allow user to select all objects on page or all matching query 2016-09-28 16:56:17 -04:00
Jeremy Stretch
c6592faeb2 Fixes #466: Validate available free space for all instances when increasing the U height of a device type 2016-09-28 14:19:52 -04:00
Jeremy Stretch
dec00cdb55 Closes #481: Require interface creation before trying to assign an IP to a device 2016-09-28 12:24:33 -04:00
Jeremy Stretch
30c7c2d359 Closes #475: Display add buttons at top and bottom of all device/device type panels 2016-09-28 12:06:00 -04:00
Jeremy Stretch
118bb5ea73 Fixed DCIM API test 2016-09-28 10:02:18 -04:00
Jeremy Stretch
35b3d8e33a Fixes #581: Corrected initialization of custom boolean and select fields 2016-09-28 09:58:59 -04:00
Jeremy Stretch
187a6dee17 Closes #579: Add a description field to ExportTemplate 2016-09-27 16:31:18 -04:00
Jeremy Stretch
0900a6bf49 Added subdevice_role to DeviceTypeSerializer 2016-09-27 16:04:14 -04:00
Jeremy Stretch
6cba2e92f2 Merge pull request #574 from rfdrake/mobile
viewport change for mobile
2016-09-27 13:58:57 -04:00
Jeremy Stretch
796b131f73 Fixes #577: Correct initialization of custom boolean fields 2016-09-27 13:42:10 -04:00
Jeremy Stretch
bdb8d62cef Closes #575: Allow all valid URL schemes in custom fields 2016-09-27 11:42:20 -04:00
Jeremy Stretch
d049c1c244 Fixes #576: Delete all relevant CustomFieldValues ehen deleting a CustomFieldChoice 2016-09-27 10:51:33 -04:00
Robert Drake
45432a6f29 viewport change for mobile 2016-09-26 23:43:05 -04:00
Jeremy Stretch
a803bd8033 Simplified web server installation doc 2016-09-26 14:21:10 -04:00
Jeremy Stretch
0001bbc966 #447: Correcting CentOS installation docs 2016-09-26 12:50:44 -04:00
Jeremy Stretch
1ebba3ee26 Merge pull request #529 from chagara/develop
Added centos/rhel installation
2016-09-26 12:05:19 -04:00
Jeremy Stretch
fde24258e3 Fixes #571: Correct rack group filter on device list 2016-09-22 10:06:59 -04:00
Jeremy Stretch
59c6d5b1ec Fixed typo 2016-09-21 11:58:04 -04:00
Jeremy Stretch
33694030b7 Tweaked ExportTemplate admin display 2016-09-21 11:57:05 -04:00
Jeremy Stretch
f8f973dac2 Post-release version bump 2016-09-21 11:45:12 -04:00
Jeremy Stretch
bffabef556 Merge pull request #570 from digitalocean/develop
Release v1.6.1-r1
2016-09-21 11:44:45 -04:00
Jeremy Stretch
325d96dabb Quick fix for v1.6.1 related to #561 2016-09-21 11:43:22 -04:00
Jeremy Stretch
b7b1682f42 Added link to the mailing list 2016-09-21 11:02:19 -04:00
Jeremy Stretch
aa2612aeba Post-release version bump 2016-09-21 10:15:03 -04:00
Jeremy Stretch
b99704082b Merge pull request #569 from digitalocean/develop
Release v1.6.1
2016-09-21 10:14:40 -04:00
Jeremy Stretch
75d8852bf7 Release v1.6.1 2016-09-21 09:55:57 -04:00
Jeremy Stretch
0444ac7db9 Introduced NullableModelMultipleChoiceField to allow null filtering without causing introspection issues during database migrations 2016-09-20 15:48:58 -04:00
Jeremy Stretch
b2684aeefc status filter fields should not be required 2016-09-20 11:29:30 -04:00
Jeremy Stretch
6ccc6244dd Corrected PrefixFilterForm 2016-09-20 11:25:16 -04:00
Jeremy Stretch
e618bf40ec Reimplemented FilterChoiceField 2016-09-20 11:08:25 -04:00
Jeremy Stretch
e3f0a12313 PEP8 fix 2016-09-19 16:21:42 -04:00
Jeremy Stretch
687e68db69 Fixes #564: Display custom fields for all applicable objects 2016-09-19 16:13:02 -04:00
Jeremy Stretch
b10e29aaac Closes #561: Make custom fields accessible from within export templates 2016-09-19 16:11:37 -04:00
Jeremy Stretch
d0c92b4f8a Removed obsolete dependency 2016-09-19 10:32:38 -04:00
Jeremy Stretch
513408f16a Fixes #562: Fixed bulk interface creation 2016-09-19 10:31:40 -04:00
Jeremy Stretch
64326e7c9d Closes #552: Added a None filter option for custom select fields 2016-09-16 13:42:07 -04:00
Jeremy Stretch
ce9d853883 Closes #415: Added an expand/collapse toggle button to the prefix list 2016-09-16 11:50:02 -04:00
Jeremy Stretch
814a0e7344 Tweak to #493 2016-09-16 10:31:42 -04:00
Jeremy Stretch
2c7c0ce29d Merge pull request #493 from stianvi/csv_reader_unicode
Fixed csv reader to handle special characters
2016-09-16 10:30:27 -04:00
Jeremy Stretch
2015d08407 Merge pull request #555 from rfdrake/develop
Fix for Docker ldap
2016-09-16 09:59:43 -04:00
Jeremy Stretch
9dea5656ad Added 'none' options to filters for optional fields 2016-09-15 17:12:53 -04:00
Jeremy Stretch
daadf7a49b Fixes #557: Add 'global' choice to VRF filter for prefixes and IP addresses 2016-09-15 16:03:53 -04:00
Jeremy Stretch
2567412121 Fixes #531: Order prefixes by VRF assignment 2016-09-15 12:09:54 -04:00
Jeremy Stretch
5e4fce248c Fixes #558: Update slug field when name is populated without a key press 2016-09-15 11:36:45 -04:00
Jeremy Stretch
824d2d8205 Implemented FilterChoiceField and get_filter_choices() to reduce filter form boilerplate 2016-09-14 16:27:26 -04:00
Robert Drake
9718895ff9 add django-auth-ldap to Dockerfile 2016-09-14 13:32:54 -04:00
Robert Drake
9eec975800 change ldap.py to ldap_config.py 2016-09-14 13:32:54 -04:00
Jeremy Stretch
5601be87f7 Merge pull request #551 from digitalocean/docker-build-perms
Fix permissions on docker-build.sh
2016-09-13 13:15:57 -04:00
Zach Moody
440610836a fixes permissions on docker-build.sh 2016-09-13 11:27:04 -05:00
Jeremy Stretch
4fa536b940 Post-release version bump 2016-09-13 12:16:42 -04:00
Jeremy Stretch
aeec678ce9 Merge pull request #550 from digitalocean/develop
Release v1.6.0
2016-09-13 12:16:00 -04:00
Jeremy Stretch
9591fb9330 PEP8 fix 2016-09-13 12:10:21 -04:00
Jeremy Stretch
cbfdd5dbd1 Relase v1.6.0 2016-09-13 12:08:36 -04:00
Jeremy Stretch
cd2fa6ed78 Simplified FibreChannel interface form factors 2016-09-13 11:21:15 -04:00
Jeremy Stretch
bef9a0c77f Added address family filters to aggregate and prefix lists 2016-09-13 10:35:55 -04:00
Jeremy Stretch
6af91b581b #167: Added more interface form factors 2016-09-12 21:34:47 -04:00
Jeremy Stretch
afe805bd28 Corrected filtering of boolean custom fields 2016-09-12 15:59:43 -04:00
Jeremy Stretch
6af7403738 Fixes #507: Corrected rendering of nav menu on screens narrower than 1200px 2016-09-12 15:02:40 -04:00
Jeremy Stretch
98fe83944f Deleted obsolete javascript 2016-09-12 13:50:55 -04:00
Jeremy Stretch
37e0388a5a Fixes #522: Remove obsolete check for staff status when bulk deleting objects 2016-09-12 11:21:13 -04:00
Jeremy Stretch
99510a990a Fixes #544: Strip CRLF-style line terminators from rendered export templates 2016-09-12 10:42:59 -04:00
Jeremy Stretch
ca6c62facd Custom fields documentation tweaks 2016-09-12 10:30:54 -04:00
Jeremy Stretch
6e5a099834 Fixes #540: Add links for VLAN roles under VLAN nav menu 2016-09-12 09:44:24 -04:00
Jeremy Stretch
e0b17b1496 Fixes #515: Clarify when 'face' field is required 2016-09-12 09:36:23 -04:00
Chagara
af519b93b7 added more changes for centos/rhel installation 2016-09-02 16:54:47 -04:00
Chagara
2213e3e0cf added centos/rhel installation on netbox.md postgresql.md and web-server.md 2016-09-02 16:38:58 -04:00
Jeremy Stretch
8341800a85 Merge pull request #504 from digitalocean/custom-fields
Custom fields
2016-08-24 09:45:50 -04:00
Jeremy Stretch
25f1fcc6cb Updated docs to add URL custom field type 2016-08-24 09:42:22 -04:00
Jeremy Stretch
d74d85a042 Added URL custom field type; added is_filterable toggle; fixed bulk editing 2016-08-23 16:45:26 -04:00
Jeremy Stretch
fcd4c9f7de Corrected reporting of bulk edits to custom fields 2016-08-23 12:47:44 -04:00
Jeremy Stretch
25c46894b4 Corrected CustomField ordering 2016-08-23 12:25:23 -04:00
Jeremy Stretch
4a2e80aeee Added a weight to CustomField for ordering fields within a form 2016-08-23 12:11:27 -04:00
Jeremy Stretch
74a5960992 Added custom field support to filter forms 2016-08-23 12:05:28 -04:00
Jeremy Stretch
28b9dda55d Implemented dynamic filters for custom fields 2016-08-23 11:18:00 -04:00
Jeremy Stretch
3b36a35b9a Fixed API tests 2016-08-22 17:15:20 -04:00
Jeremy Stretch
b9dcf9ca12 Refreshed migrations 2016-08-22 15:52:26 -04:00
Jeremy Stretch
faed3c1314 Merged develop 2016-08-22 15:49:41 -04:00
Jeremy Stretch
f0a85b1dd3 Optimized API performance 2016-08-22 15:16:49 -04:00
Jeremy Stretch
76f0463290 Extended API to include custom fields 2016-08-22 13:20:30 -04:00
Jeremy Stretch
b14afaa687 Updated bulk edit forms to support custom fields 2016-08-22 13:11:57 -04:00
Jeremy Stretch
14a908bf66 Merge pull request #496 from rfdrake/develop
fix for Dockerfile
2016-08-22 09:11:05 -04:00
Robert Drake
8f34b6b0b9 fix for Dockerfile
It was hard to test with the old syntax.  It was cloning the "master"
branch, so trying to test a development change was difficult.
I believe I've fixed it so that the "master" branch and "develop"
branch can use the same Dockerfile options.  You override which branch
it pulls by setting a build-args variable, either via docker-compose or
in the docker build options.
2016-08-21 23:48:27 -04:00
Jeremy Stretch
eab18a81c9 Adjusted display of created and last_updated times for primary objects 2016-08-19 12:19:02 -04:00
Jeremy Stretch
7a558d8332 Fixes #490: Corrected display of circuit commit rate 2016-08-19 10:41:37 -04:00
Jeremy Stretch
fa79014585 Merge pull request #489 from rfdrake/develop
Changes to Dockerfile to make the build faster
2016-08-19 10:33:19 -04:00
Jeremy Stretch
8cf2ae7851 Fixes #495: Include tenant in prefix and IP CSV export 2016-08-19 10:31:16 -04:00
Stian Vikan
0708942ab8 Fixed csv reader to handle special characters 2016-08-19 12:09:40 +02:00
Robert Drake
4b4602b703 Changes to Dockerfile to make the build faster
To download a new version with docker, I've been running

	git pull
	docker-compose build --no-cache

This is slow, but no-cache is needed so that "git clone" pulls the
latest copy.

Most of the slowness comes from pulling down apt files each time a
rebuild needs to be done.  If we move that into a docker image then only
the local changes need to be rebuilt.

Further refinements could be done.  If the python dependencies that are
brought in from requirements.txt could be moved to an image then nothing
would change between updates as long as dependant versions hadn't
changed.  This would probably be more trouble than it's worth, unless
you're recreating netbox containers 10-20 times a day.
2016-08-18 23:49:41 -04:00
Jeremy Stretch
ab90a06c54 Fixes #486: Prompt for secret key only if updating a secret's value 2016-08-18 16:43:41 -04:00
Jeremy Stretch
6dbf2043b9 Merge pull request #488 from digitalocean/revert-478-develop
Revert "Changes to Dockerfile to make the build faster"
2016-08-18 15:32:39 -04:00
Jeremy Stretch
041a166217 Revert "Changes to Dockerfile to make the build faster" 2016-08-18 15:31:31 -04:00
Jeremy Stretch
63ac4e2c42 Fixes #476: Corrected rack import instructions 2016-08-18 15:23:28 -04:00
Jeremy Stretch
77d721360f Merge pull request #478 from rfdrake/develop
Changes to Dockerfile to make the build faster
2016-08-18 15:18:30 -04:00
Jeremy Stretch
1d6299622b Documentation and cleanup 2016-08-18 14:23:28 -04:00
Jeremy Stretch
6f44f4245e Fixed default value for boolean fields 2016-08-18 11:44:40 -04:00
Jeremy Stretch
de8fd550cb Fixes #484: Allow bulk deletion of >1K objects 2016-08-18 10:12:43 -04:00
Jeremy Stretch
9bdb50c33e Optimized bulk editing of custom fields 2016-08-17 15:52:27 -04:00
Robert Drake
6ed33af063 Changes to Dockerfile to make the build faster
To download a new version with docker, I've been running

	git pull
	docker-compose build --no-cache

This is slow, but no-cache is needed so that "git clone" pulls the
latest copy.

Most of the slowness comes from pulling down apt files each time a
rebuild needs to be done.  If we move that into a docker image then only
the local changes need to be rebuilt.

Further refinements could be done.  If the python dependencies that are
brought in from requirements.txt could be moved to an image then nothing
would change between updates as long as dependant versions hadn't
changed.  This would probably be more trouble than it's worth, unless
you're recreating netbox containers 10-20 times a day.
2016-08-17 15:19:56 -04:00
Jeremy Stretch
a33e89fed7 Converted to a single column for value storage 2016-08-17 14:49:42 -04:00
Jeremy Stretch
b0a325f173 More performance improvements 2016-08-17 13:40:22 -04:00
Jeremy Stretch
b7a90dd09a Added icon for boolean fields 2016-08-17 13:40:06 -04:00
Jeremy Stretch
8d99ad3099 Corrected issue with duplicate queries 2016-08-17 12:41:12 -04:00
Jeremy Stretch
c49177e59c Merge branch 'develop' into custom-fields 2016-08-17 12:05:13 -04:00
Jeremy Stretch
96d6be3608 Merge branch 'develop' of github.com:digitalocean/netbox into develop 2016-08-17 12:04:26 -04:00
Jeremy Stretch
aa84d04c8b Removed redundant PK list from bulk edit template 2016-08-17 12:04:16 -04:00
Jeremy Stretch
c8b85202d1 Fixed test case name 2016-08-16 17:49:52 -04:00
Jeremy Stretch
af459cd19b Added some simple tests for custom fields 2016-08-16 17:48:35 -04:00
Zach Moody
c056d86b24 Merge pull request #471 from joonas/travis/automated-docker-builds
travis: Automate docker builds
2016-08-16 15:54:24 -05:00
Jeremy Stretch
7d879bb0dc Added bulk editing capability for custom fields 2016-08-16 14:57:04 -04:00
Jeremy Stretch
a9a55350df Merging v1.5.2 2016-08-16 10:19:55 -04:00
Jeremy Stretch
f019253c8e Post-release version bump 2016-08-16 09:34:26 -04:00
Jeremy Stretch
58e3d5ae09 Merge pull request #474 from digitalocean/develop
Release v1.5.2
2016-08-16 09:33:57 -04:00
Jeremy Stretch
2eb8b4fe71 Release v1.5.2 2016-08-16 09:32:53 -04:00
Jeremy Stretch
989ec721d3 Fixes #472: Hide the connection button for interfaces which have a circuit terminated to them 2016-08-16 09:29:20 -04:00
Joonas Bergius
4fc0fd9a9a Produce a docker image when there's a new tag 2016-08-15 22:00:34 -04:00
Joonas Bergius
5afb98ffa7 Added scripts/docker-build.sh for building docker images in CI 2016-08-15 21:59:01 -04:00
Jeremy Stretch
d045429b51 Fixes #469: Added missing import buttons to list views 2016-08-15 17:57:17 -04:00
Jeremy Stretch
c60c4ad0df Added templates for IPAM objects; cleaned up admin 2016-08-15 16:58:25 -04:00
Jeremy Stretch
78c3b25f0a Fixes #467: Include prefixes and IPs which inherit tenancy from their VRF in tenant stats 2016-08-15 16:11:17 -04:00
Jeremy Stretch
13136d0ccb Fixes #468: Added validation to prevent a connected interface from having its form factor set to 'virtual' 2016-08-15 15:52:06 -04:00
Jeremy Stretch
8faa16c831 Fixes #460: For real this time 2016-08-15 15:39:48 -04:00
Jeremy Stretch
6cdb62b67e Minimal implemtnation of custom fields 2016-08-15 15:24:23 -04:00
Jeremy Stretch
4f774f8ba6 Fixes #460: Corrected ordering of IP addresses with differing prefix lengths 2016-08-13 01:02:03 -04:00
Jeremy Stretch
550a05487d Initial work on custom fields 2016-08-12 17:20:01 -04:00
Jeremy Stretch
bf1b8ab9b8 Enable custom export templates for Tenants 2016-08-12 13:29:24 -04:00
Jeremy Stretch
b74f338aa1 Fixes #463: Prevent prepopulation of livesearch field with '---------' 2016-08-12 11:24:29 -04:00
Jeremy Stretch
35aa8acd09 Post-release version bump 2016-08-11 13:54:07 -04:00
Jeremy Stretch
6a48b310d2 Merge pull request #459 from digitalocean/develop
Release v1.5.1
2016-08-11 13:53:34 -04:00
Jeremy Stretch
0b4d3446bf Release v1.5.1 2016-08-11 13:47:09 -04:00
Jeremy Stretch
2b8e06faa2 Added Device asset_tag field to API tests 2016-08-11 13:46:44 -04:00
Jeremy Stretch
c86a1123f0 Closes #421: Added asset_tag field to devices 2016-08-11 11:58:21 -04:00
Jeremy Stretch
e2ad1d4be0 Closes #456: Added IP search box to home page 2016-08-11 11:06:56 -04:00
Jeremy Stretch
219f084805 Fixes #457: Added role field to rack edit form 2016-08-11 10:19:50 -04:00
Jeremy Stretch
6f12297dcf Fixes #441: Added openssl-devel to list of dependencies 2016-08-10 22:37:51 -04:00
Jeremy Stretch
098ff961e3 Fixes #454: Correct typecasting on rack export 2016-08-10 22:27:27 -04:00
Jeremy Stretch
74528c6036 Colorized roles in rack and device lists 2016-08-10 22:20:09 -04:00
Jeremy Stretch
3324f397d9 Post-release version bump 2016-08-10 17:48:14 -04:00
Jeremy Stretch
2509405465 Merge pull request #453 from digitalocean/develop
Release v1.5.0
2016-08-10 17:47:48 -04:00
Jeremy Stretch
0f779dd682 Release v1.5.0 2016-08-10 17:41:08 -04:00
Jeremy Stretch
6bf8a7707c Reverting to psycopg2==2.6.1 due to pip installation issues 2016-08-10 17:30:31 -04:00
Jeremy Stretch
8ff752a58b Updated requirements for v1.5 2016-08-10 16:42:42 -04:00
Jeremy Stretch
7938c9211f Removed obsolete setting LOGOUT_URL 2016-08-10 16:08:02 -04:00
Jeremy Stretch
1f4d5b84b3 Added status field to DeviceTestCase tests (Django 1.10 BooleanField issue) 2016-08-10 16:04:44 -04:00
Jeremy Stretch
3a018888c8 Updated docs for new features 2016-08-10 14:09:38 -04:00
Jeremy Stretch
a89fb734a0 Added colored labels for device and rack roles 2016-08-10 13:35:55 -04:00
Jeremy Stretch
b2f03dfb16 Fixed redirect after editing RackGroups and VLANGroups 2016-08-10 13:13:26 -04:00
Jeremy Stretch
cbde6e8321 Miscellaneous query optimizations 2016-08-10 13:07:11 -04:00
Jeremy Stretch
ed03449164 Closes #241: Introduced rack roles 2016-08-10 11:52:27 -04:00
Jeremy Stretch
47a89999b8 Extended DCIM API to include a device modules list 2016-08-10 10:44:37 -04:00
Jeremy Stretch
a7dd2695a2 Closes #157: Added manufacturer field to module model 2016-08-10 10:24:47 -04:00
Jeremy Stretch
8035538b74 Fixes #445: Limit rack height to between 1U and 100U (inclusive) 2016-08-09 17:19:23 -04:00
Jeremy Stretch
474b19d927 We have a logo! 2016-08-09 14:57:14 -04:00
Jeremy Stretch
31ebbb3324 Fixes #444: Corrected prefix model validation 2016-08-09 09:50:50 -04:00
Jeremy Stretch
bddd29c99f Fixes #443: Correctly display and initialize VRF for creation of new IP addresses 2016-08-08 21:44:54 -04:00
Jeremy Stretch
e7116b81a4 #180: Added type and width fields to Rack model 2016-08-08 18:01:15 -04:00
Jeremy Stretch
d463161619 Closes #149: Added upstream_speed field to Circuit 2016-08-08 16:51:19 -04:00
Jeremy Stretch
5116db3344 Fixes #442: Correct child device import instructions 2016-08-08 12:28:38 -04:00
Jeremy Stretch
b131fbd774 Corred typo in HTML 2016-08-08 12:04:20 -04:00
Jeremy Stretch
324a5e10d7 Fixes #433: Correct form validation when editing child devices 2016-08-08 09:45:44 -04:00
Jeremy Stretch
69debfdefa Post-release version bump 2016-08-06 16:32:54 -04:00
Jeremy Stretch
93fccd5985 Merge pull request #438 from digitalocean/develop
Release v1.4.2
2016-08-06 16:31:32 -04:00
Jeremy Stretch
e55acf8c63 Migration for new interface form factors added in #167 2016-08-06 16:27:00 -04:00
Jeremy Stretch
c19e358eef Making PEP8 happy 2016-08-06 16:19:44 -04:00
Jeremy Stretch
efe7b46021 Release v1.4.2 2016-08-06 16:16:35 -04:00
Jeremy Stretch
ededd3f464 Fixes #253: Added ability to search by prefix to IP address filters 2016-08-06 16:02:57 -04:00
Jeremy Stretch
ac2aa7ea89 Fixes #435: Added a "add prefix" button to the VLAN view 2016-08-06 15:44:28 -04:00
Jeremy Stretch
04c9ebd46d Fixes #434: Increased user actions history on home page from 15 to 50; restored admin UI access but disabled bulk deletion function 2016-08-06 15:35:13 -04:00
Jeremy Stretch
c3c3b80cd9 Fixed toggling of secret lock/unlock buttons 2016-08-04 14:42:34 -04:00
Jeremy Stretch
29c4394e64 Fixes #429: Correct redirection of user when adding a secret to a device 2016-08-04 14:37:38 -04:00
Jeremy Stretch
76b9a1c3af #167: Added new interface form factors 2016-08-04 13:38:45 -04:00
Jeremy Stretch
6184eb6664 Fixes #425: Ignore leading and trailing periods when generating a slug 2016-08-04 11:52:55 -04:00
Jeremy Stretch
e413012cbb Fixes #427: Prevent error when duplicate IPs are present in a prefix's IP list 2016-08-04 11:48:30 -04:00
Jeremy Stretch
ea2e734ba8 Post-release version bump 2016-08-03 17:49:28 -04:00
Jeremy Stretch
4ee63f4ff8 Post-release version bump 2016-08-03 17:49:08 -04:00
Jeremy Stretch
946a1b751b Merge pull request #423 from digitalocean/develop
Release v1.4.1
2016-08-03 17:46:13 -04:00
Jeremy Stretch
4ab40c4489 Release v1.4.1 2016-08-03 17:39:57 -04:00
Jeremy Stretch
7944ee6419 Fixes #422: Added ability to encapsulate within double quotes values which contain commas 2016-08-03 17:33:15 -04:00
Jeremy Stretch
72690bfd0a Potential fix for #419: Ditch annotation in favor of discrete queries to gather Tenant stats 2016-08-03 14:24:09 -04:00
Jeremy Stretch
0f0d0c150a Merge pull request #420 from digitalocean/free_ip_ranges
Fixes #289: Annotate free IP ranges within a prefix
2016-08-03 14:09:36 -04:00
Jeremy Stretch
3b9ac3b986 More intelligent handling of first/last IPs 2016-08-03 12:30:29 -04:00
Jeremy Stretch
79b1bbb9e1 Fixed calculation of available IPs between two existing IPs 2016-08-03 12:20:24 -04:00
Jeremy Stretch
533b4082d8 Fixed calculation of last_ip_in_prefix for IPv6 2016-08-03 12:06:17 -04:00
Jeremy Stretch
81d955ab7d Rewrote add_available_ipaddresses() to be much more efficient and IPv6-friendly 2016-08-03 12:00:35 -04:00
Jeremy Stretch
57373c9d6f Initial work on #289 2016-08-02 17:20:12 -04:00
Jeremy Stretch
bc9158a74f Closes #412: Tenant group assignment is no longer mandatory 2016-08-02 16:04:25 -04:00
Jeremy Stretch
9f3647cd53 Addresses #395: Show child prefixes from all VRFs if the parent prefix is in the global table 2016-08-02 15:48:12 -04:00
Jeremy Stretch
d294e916a4 Fixes #406: Corrected ordering of port_speed and commit_rate in CircuitTable 2016-08-02 10:50:25 -04:00
Jeremy Stretch
249faffe42 Fixes #409: Filter IPs and prefixes by tenant slug rather than by its PK 2016-08-02 10:39:42 -04:00
Jeremy Stretch
3327954a34 Fixes #411: Corrected name of secret roles page 2016-08-02 10:27:58 -04:00
Jeremy Stretch
3b76377cac Post-release version bump 2016-08-01 13:44:22 -04:00
Jeremy Stretch
9889e120bd Merge pull request #408 from digitalocean/develop
Release v1.4.0
2016-08-01 13:43:48 -04:00
Jeremy Stretch
00e0fb5798 Pre-release version bump 2016-08-01 13:35:49 -04:00
Jeremy Stretch
1fd189f9b1 Replaced most glyphicons with Font Awesome 2016-08-01 13:29:45 -04:00
Jeremy Stretch
b73f980eb2 Closes #176: Added initial_data fixtures for new installs 2016-08-01 12:29:26 -04:00
Jeremy Stretch
65ea2af4b7 Partial conversion from glyphicons to font awesome 2016-07-29 18:04:38 -04:00
Jeremy Stretch
8a9c6ce37a Standardized display of attributes for primary objects 2016-07-29 15:31:35 -04:00
Jeremy Stretch
1bbe7f95d6 PEP8 cleanup 2016-07-29 14:46:29 -04:00
Jeremy Stretch
d09ede8d1f Corrected omitted variable 2016-07-29 14:29:13 -04:00
Jeremy Stretch
bcb9ab7116 Show graphs button only if there is at least one graph to display 2016-07-29 13:19:58 -04:00
Jeremy Stretch
75c3e62ca8 Changed VRF and VLAN views to use PrefixBriefTable 2016-07-29 13:03:44 -04:00
Jeremy Stretch
38aee33df0 Closes #358: Improved search of all objects 2016-07-29 12:51:23 -04:00
Jeremy Stretch
2daffdf087 Added account field to provider table 2016-07-29 12:33:40 -04:00
Jeremy Stretch
03d71f9764 Standardized breadcrumb hierarchies 2016-07-29 11:20:47 -04:00
Jeremy Stretch
fa906c74c0 Fixed actions column alignment 2016-07-29 10:57:09 -04:00
Jeremy Stretch
d933d034e0 Changed TenantGroup edit link to a button 2016-07-28 16:03:59 -04:00
Jeremy Stretch
397943b222 Allow unassigning VRF and tenants when editing objects in bulk 2016-07-28 15:59:49 -04:00
Jeremy Stretch
6b41794e12 Implemented bulk editing for sites 2016-07-28 15:30:29 -04:00
Jeremy Stretch
b6e5bafd65 Replaced edit links with buttons 2016-07-28 15:04:33 -04:00
Jeremy Stretch
e6c06b39e8 Adds tenant assignment to Prefix and IPAddress objects 2016-07-28 13:50:46 -04:00
Jeremy Stretch
a25534f3de Fixes #397: Only include child IPs which belong to the same VRF as the parent prefix 2016-07-28 11:24:25 -04:00
Jeremy Stretch
aa6c840c45 Fixes #392: Don't include child devices in non-racked devices table 2016-07-28 10:06:25 -04:00
Jeremy Stretch
aee9314bbf Added tenancy page 2016-07-28 09:47:16 -04:00
Jeremy Stretch
3bb10bca1b Linkified VRF column in prefix and IP address tables 2016-07-27 16:52:20 -04:00
Jeremy Stretch
1f9e4dc707 Fixed platform selection during bulk editing of devices 2016-07-27 16:38:21 -04:00
Jeremy Stretch
76efea87ff Closes #394: Added global option to VRF selection widget during bulk editing 2016-07-27 16:29:30 -04:00
Jeremy Stretch
483ad256a8 Miscellaneous API query optimizations 2016-07-27 14:40:19 -04:00
Jeremy Stretch
618566abe8 Added VRF stats to home page 2016-07-27 14:13:07 -04:00
Jeremy Stretch
1413f5d89e Merge pull request #393 from digitalocean/multitenancy
Multitenancy
2016-07-27 14:05:48 -04:00
Jeremy Stretch
4cc84aed5a PEP8 fix 2016-07-27 13:59:18 -04:00
Jeremy Stretch
300e67388b Tenancy-related API cleanup 2016-07-27 13:42:17 -04:00
Jeremy Stretch
2981ead41b Extended IPAM API to support tenancy 2016-07-27 13:37:55 -04:00
Jeremy Stretch
e4960873f3 Added stats to tenant view 2016-07-27 11:56:47 -04:00
Jeremy Stretch
2abee211a2 Implemented tenancy for VRFs and VLANs 2016-07-27 11:29:20 -04:00
Jeremy Stretch
65b008a493 Cleaned up migrations 2016-07-26 18:01:01 -04:00
Jeremy Stretch
2236d2f941 Fixed tenant assignment on bulk edit of racks, devices 2016-07-26 17:49:41 -04:00
Jeremy Stretch
41b2b7dbf6 Fixed Tenant import 2016-07-26 17:47:40 -04:00
Jeremy Stretch
27c21237ff Added description to Tenant model 2016-07-26 17:44:32 -04:00
Jeremy Stretch
faa12abc70 Enabled filtering of sites, racks, and devices by tenant 2016-07-26 17:28:46 -04:00
Jeremy Stretch
7ca4c816c0 Added related_name to tenant fields on Site, Rack, and Device 2016-07-26 17:16:03 -04:00
Jeremy Stretch
6f68628377 Added tenant to circuit bulk editing; enabled filtering of circuits by tenant 2016-07-26 17:10:11 -04:00
Jeremy Stretch
82a98f0e8f Applied tenancy to sites, racks, and devices 2016-07-26 16:46:22 -04:00
Jeremy Stretch
1939db1574 Added tenant to import/export of Circuits 2016-07-26 15:48:48 -04:00
Jeremy Stretch
06af05708a Applied tenancy to circuits 2016-07-26 15:42:26 -04:00
Jeremy Stretch
fa2ccc1c18 Initial multitenancy implementation 2016-07-26 14:58:37 -04:00
Jeremy Stretch
b790d7d50f Post-release version bump 2016-07-26 12:24:32 -04:00
Jeremy Stretch
af5dba2e0d Merge pull request #386 from digitalocean/develop
Release v1.3.2
2016-07-26 12:22:29 -04:00
Jeremy Stretch
8cb38de7d5 Merge pull request #357 from digitalocean/develop
Release v1.3.1
2016-07-21 11:48:40 -04:00
Jeremy Stretch
5ba5e8def9 Merge pull request #324 from digitalocean/develop
Release v1.3.0
2016-07-18 13:49:08 -04:00
Jeremy Stretch
4e64e1ea95 Merge pull request #299 from digitalocean/develop
Release v1.2.2
2016-07-14 15:21:40 -04:00
Jeremy Stretch
300aff71bb Merge pull request #286 from digitalocean/develop
Release v1.2.1
2016-07-13 12:08:48 -04:00
Jeremy Stretch
0c3970233e Merge pull request #269 from digitalocean/develop
Release v1.2.0
2016-07-12 11:37:56 -04:00
622 changed files with 56443 additions and 24673 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.sh text eol=lf

48
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,48 @@
<!--
Before opening a new issue, please search through the existing issues to
see if your topic has already been addressed. Note that you may need to
remove the "is:open" filter from the search bar to include closed issues.
Check the appropriate type for your issue below by placing an x between the
brackets. For assistance with installation issues, or for any other issues
other than those listed below, please raise your topic for discussion on
our mailing list:
https://groups.google.com/forum/#!forum/netbox-discuss
Please note that issues which do not fall under any of the below categories
will be closed. Due to an excessive backlog of feature requests, we are
not currently accepting any proposals which extend NetBox's feature scope.
Do not prepend any sort of tag to your issue's title. An administrator will
review your issue and assign labels as appropriate.
--->
### Issue type
[ ] Feature request <!-- An enhancement of existing functionality -->
[ ] Bug report <!-- Unexpected or erroneous behavior -->
[ ] Documentation <!-- A modification to the documentation -->
<!--
Please describe the environment in which you are running NetBox. (Be sure
to verify that you are running the latest stable release of NetBox before
submitting a bug report.) If you are submitting a bug report and have made
any changes to the code base, please first validate that your bug can be
recreated while running an official release.
-->
### Environment
* Python version: <!-- Example: 3.5.4 -->
* NetBox version: <!-- Example: 2.1.3 -->
<!--
BUG REPORTS must include:
* A list of the steps needed for someone else to reproduce the bug
* A description of the expected and observed behavior
* Any relevant error messages (screenshots may also help)
FEATURE REQUESTS must include:
* A detailed description of the proposed functionality
* A use case for the new feature
* A rough description of any necessary changes to the database schema
* Any relevant third-party libraries which would be needed
-->
### Description

14
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,14 @@
<!--
Thank you for your interest in contributing to NetBox! Please note
that our contribution policy requires that a feature request or bug
report be opened for approval prior to filing a pull request. This
helps avoid wasting time and effort on something that we might not
be able to accept.
Please indicate the relevant feature request or bug report below.
-->
### Fixes:
<!--
Please include a summary of the proposed changes below.
-->

7
.gitignore vendored
View File

@@ -1,7 +1,12 @@
*.pyc
configuration.py
/netbox/netbox/configuration.py
/netbox/netbox/ldap_config.py
/netbox/reports/*
!/netbox/reports/__init__.py
/netbox/static
.idea
/*.sh
!upgrade.sh
fabfile.py
*.swp
gunicorn_config.py

View File

@@ -1,8 +1,17 @@
sudo: required
services:
- postgresql
addons:
postgresql: "9.4"
language: python
python:
- "2.7"
- "3.5"
install:
- pip install -r requirements.txt
- pip install pep8
before_script:
- psql --version
- psql -U postgres -c 'SELECT version();'
script:
- ./scripts/cibuild.sh

View File

@@ -1,84 +1,117 @@
## Getting Help
If you encounter any issues installing or using NetBox, try one of the following resources to get assistance. Please
**do not** open an issue on GitHub except to report bugs or request features.
If you encounter any issues installing or using NetBox, try one of the
following resources to get assistance. Please **do not** open a GitHub issue
except to report bugs or request features.
### Freenode IRC
### Mailing List
Join the #netbox channel on [Freenode IRC](https://freenode.net/). You can connect to Freenode at irc.freenode.net using
an IRC client, or you can use their [webchat client](https://webchat.freenode.net/).
We have established a Google Groups Mailing List for issues and general
discussion. This is the best forum for obtaining assistance with NetBox
installation. You can find us [here](https://groups.google.com/forum/#!forum/netbox-discuss).
### Reddit
### Slack
We have established [/r/netbox](https://www.reddit.com/r/netbox) on Reddit for NetBox issues and general discussion.
Reddit registration is free and does not require providing an email address (although it is encouraged).
For real-time discussion, you can join the #netbox Slack channel on [NetworkToCode](https://slack.networktocode.com/).
## Reporting Bugs
* First, ensure that you've installed the [latest stable version](https://github.com/digitalocean/netbox/releases) of
NetBox. If you're running an older version, it's possible that the bug has already been fixed.
* First, ensure that you've installed the [latest stable version](https://github.com/digitalocean/netbox/releases)
of NetBox. If you're running an older version, it's possible that the bug has
already been fixed.
* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has
already been reported. If you think you may be experiencing a reported issue that hasn't already been resolved, please
click "add a reaction" in the top right corner of the issue and add a thumbs up (+1). You might also want to add a
comment describing how it's affecting your installation. This will allow us to prioritize bugs based on how many users
are affected.
* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues)
to see if the bug you've found has already been reported. If you think you may
be experiencing a reported issue that hasn't already been resolved, please
click "add a reaction" in the top right corner of the issue and add a thumbs
up (+1). You mightalso want to add a comment describing how it's affecting your
installation. This will allow us to prioritize bugs based on how many users are
affected.
* If you haven't found an existing issue that describes your suspected bug, please inquire about it on IRC or Reddit.
**Do not** file an issue until you have received confirmation that it is in fact a bug. Invalid issues are very
* If you haven't found an existing issue that describes your suspected bug,
please inquire about it on the mailing list. **Do not** file an issue until you
have received confirmation that it is in fact a bug. Invalid issues are very
distracting and slow the pace at which NetBox is developed.
* When submitting an issue, please be as descriptive as possible. Be sure to include:
* When submitting an issue, please be as descriptive as possible. Be sure to
include:
* The environment in which NetBox is running
* The exact steps that can be taken to reproduce the issue (if applicable)
* Any error messages returned
* Any error messages generated
* Screenshots (if applicable)
* Keep in mind that we prioritize bugs based on their severity and how much work is required to resolve them. It may
take some time for someone to address your issue.
* Please avoid prepending any sort of tag (e.g. "[Bug]") to the issue title.
The issue will be reviewed by a moderator after submission and the appropriate
labels will be applied for categorization.
* Keep in mind that we prioritize bugs based on their severity and how much
work is required to resolve them. It may take some time for someone to address
your issue.
## Feature Requests
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're
requesting is already listed. (Be sure to search closed issues as well, since some feature requests are rejected.) If
the feature you'd like to see has already been requested, click "add a reaction" in the top right corner of the issue
and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel free
to add a comment with any additional justification for the feature.
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues)
to see if the feature you're requesting is already listed. (Be sure to search
closed issues as well, since some feature requests have been rejected.) If the
feature you'd like to see has already been requested and is open, click "add a
reaction" in the top right corner of the issue and add a thumbs up (+1). This
ensures that the issue has a better chance of receiving attention. Also feel
free to add a comment with any additional justification for the feature.
(However, note that comments with no substance other than a "+1" will be
deleted. Please use GitHub's reactions feature to indicate your support.)
* While suggestions for new features are welcome, it's important to limit the scope of NetBox's feature set to avoid
feature creep. For example, the following features would be firmly out of scope for NetBox:
* Due to an excessive backlog of feature requests, we are not currently
accepting any proposals which substantially extend NetBox's functionality
beyond its current feature set. This includes the introduction of any new views
or models which have not already been proposed in an existing feature request.
* Ticket management
* Network state monitoring
* Acting as a DNS server
* Acting as an authentication server
* Before filing a new feature request, consider raising your idea on the
mailing list first. Feedback you receive there will help validate and shape the
proposed feature before filing a formal issue.
* Before filing a new feature request, propose it on IRC or Reddit first. Feedback you receive there will help validate
and shape the proposed feature before filing a formal issue.
* Good feature requests are very narrowly defined. Be sure to thoroughly
describe the functionality and data model(s) being proposed. The more effort
you put into writing a feature request, the better its chance is of being
implemented. Overly broad feature requests will be closed.
* Good feature requests are very narrowly defined. Be sure to enumerate specific functionality and data schema. The more
effort you put into writing a feature request, the better its chances are of being implemented. Overly broad feature
requests will be closed.
* When submitting a feature request on GitHub, be sure to include the following:
* When submitting a feature request on GitHub, be sure to include the
following:
* A detailed description of the proposed functionality
* A use case for the feature; who would use it and what value it would add to NetBox
* A rough description of any changes necessary to the database schema
* A use case for the feature; who would use it and what value it would add
to NetBox
* A rough description of changes necessary to the database schema (if
applicable)
* Any third-party libraries or other resources which would be involved
* Please avoid prepending any sort of tag (e.g. "[Feature]") to the issue
title. The issue will be reviewed by a moderator after submission and the
appropriate labels will be applied for categorization.
## Submitting Pull Requests
* Be sure to open an issue before starting work on a pull request, and discuss your idea with the NetBox maintainers
before beginning work. This will help prevent wasting time on something that might we might not be able to implement.
When suggesting a new feature, also make sure it won't conflict with any work that's already in progress.
* Be sure to open an issue before starting work on a pull request, and discuss
your idea with the NetBox maintainers before beginning work. This will help
prevent wasting time on something that might we might not be able to implement.
When suggesting a new feature, also make sure it won't conflict with any work
that's already in progress.
* When submitting a pull request, please be sure to work off of the `develop` branch, rather than `master`. In NetBox,
the `develop` branch is used for ongoing development, while `master` is used for tagging new stable releases.
* When submitting a pull request, please be sure to work off of the `develop`
branch, rather than `master`. The `develop` branch is used for ongoing
development, while `master` is used for tagging new stable releases.
* All code submissions should meet the following criteria (CI will enforce these checks):
* All code submissions should meet the following criteria (CI will enforce
these checks):
* Python syntax is valid
* All tests pass when run with `./manage.py test netbox/`
* PEP 8 compliance is enforced, with the exception that lines may be greater than 80 characters in length
* All tests pass when run with `./manage.py test`
* PEP 8 compliance is enforced, with the exception that lines may be
greater than 80 characters in length
## Commenting
Only comment on an issue if you are sharing a relevant idea or constructive
feedback. **Do not** comment on an issue just to show your support (give the
top post a :+1: instead) or ask for an ETA. These comments will be deleted to
reduce noise in the discussion.

View File

@@ -1,30 +0,0 @@
FROM ubuntu:14.04
RUN apt-get update && apt-get install -y \
python2.7 \
python-dev \
git \
python-pip \
libxml2-dev \
libxslt1-dev \
libffi-dev \
graphviz \
libpq-dev \
build-essential \
gunicorn \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /opt/netbox \
&& cd /opt/netbox \
&& git clone --depth 1 https://github.com/digitalocean/netbox.git -b master . \
&& pip install -r requirements.txt \
&& apt-get purge -y --auto-remove git build-essential
ADD docker/docker-entrypoint.sh /docker-entrypoint.sh
ADD netbox/netbox/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py
ENTRYPOINT [ "/docker-entrypoint.sh" ]
ADD docker/gunicorn_config.py /opt/netbox/
ADD docker/nginx.conf /etc/netbox-nginx/
VOLUME ["/etc/netbox-nginx/"]

View File

@@ -1,16 +1,24 @@
# NetBox
![NetBox](docs/netbox_logo.png "NetBox logo")
NetBox is an IP address management (IPAM) and data center infrastructure management (DCIM) tool. Initially conceived by the network engineering team at [DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically to address the needs of network and infrastructure engineers.
NetBox is an IP address management (IPAM) and data center infrastructure
management (DCIM) tool. Initially conceived by the network engineering team at
[DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically
to address the needs of network and infrastructure engineers.
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/) Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/digitalocean/netbox).
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/)
Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a
complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/digitalocean/netbox).
The complete documentation for Netbox can be found at [Read the Docs](http://netbox.readthedocs.io/en/latest/).
The complete documentation for NetBox can be found at [Read the Docs](http://netbox.readthedocs.io/en/stable/).
Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net**!
Questions? Comments? Please subscribe to [the netbox-discuss mailing list](https://groups.google.com/forum/#!forum/netbox-discuss),
or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode.slack.com/)!
### Build Status
| | python 2.7 |
NetBox is built against both Python 2.7 and 3.5. Python 3.5 is recommended.
| | status |
|-------------|------------|
| **master** | [![Build Status](https://travis-ci.org/digitalocean/netbox.svg?branch=master)](https://travis-ci.org/digitalocean/netbox) |
| **develop** | [![Build Status](https://travis-ci.org/digitalocean/netbox.svg?branch=develop)](https://travis-ci.org/digitalocean/netbox) |
@@ -25,6 +33,12 @@ Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net*
# Installation
Please see [the documentation](http://netbox.readthedocs.io/en/latest/) for instructions on installing NetBox.
Please see [the documentation](http://netbox.readthedocs.io/en/stable/) for
instructions on installing NetBox. To upgrade NetBox, please download the [latest release](https://github.com/digitalocean/netbox/releases)
and run `upgrade.sh`.
To upgrade NetBox, please download the [latest release](https://github.com/digitalocean/netbox/releases) and run `upgrade.sh`.
## Alternative Installations
* [Docker container](https://github.com/ninech/netbox-docker) (via [@cimnine](https://github.com/cimnine))
* [Vagrant deployment](https://github.com/ryanmerolle/netbox-vagrant) (via [@ryanmerolle](https://github.com/ryanmerolle))
* [Ansible deployment](https://github.com/lae/ansible-role-netbox) (via [@lae](https://github.com/lae))

View File

@@ -1,53 +0,0 @@
version: '2'
services:
postgres:
image: postgres:9.6
container_name: postgres
environment:
POSTGRES_USER: netbox
POSTGRES_PASSWORD: J5brHrAXFLQSif0K
POSTGRES_DB: netbox
netbox:
build: .
image: digitalocean/netbox
links:
- postgres
container_name: netbox
depends_on:
- postgres
environment:
SUPERUSER_NAME: admin
SUPERUSER_EMAIL: admin@example.com
SUPERUSER_PASSWORD: admin
ALLOWED_HOSTS: localhost
DB_NAME: netbox
DB_USER: netbox
DB_PASSWORD: J5brHrAXFLQSif0K
DB_HOST: postgres
SECRET_KEY: r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
EMAIL_SERVER: localhost
EMAIL_PORT: 25
EMAIL_USERNAME: foo
EMAIL_PASSWORD: bar
EMAIL_TIMEOUT: 10
EMAIL_FROM: netbox@bar.com
NETBOX_USERNAME: guest
NETBOX_PASSWORD: guest
volumes:
- netbox-static-files:/opt/netbox/netbox/static
nginx:
image: nginx:1.11.1-alpine
links:
- netbox
container_name: nginx
command: nginx -g 'daemon off;' -c /etc/netbox-nginx/nginx.conf
depends_on:
- netbox
ports:
- 80:80
volumes_from:
- netbox
volumes:
netbox-static-files:
driver: local

View File

@@ -1,22 +0,0 @@
#!/bin/bash
set -e
# run db migrations (retry on error)
while ! /opt/netbox/netbox/manage.py migrate 2>&1; do
sleep 5
done
# create superuser silently
if [[ -z ${SUPERUSER_NAME} || -z ${SUPERUSER_EMAIL} || -z ${SUPERUSER_PASSWORD} ]]; then
SUPERUSER_NAME='admin'
SUPERUSER_EMAIL='admin@example.com'
SUPERUSER_PASSWORD='admin'
echo "Using defaults: Username: ${SUPERUSER_NAME}, E-Mail: ${SUPERUSER_EMAIL}, Password: ${SUPERUSER_PASSWORD}"
fi
echo "from django.contrib.auth.models import User; User.objects.create_superuser('${SUPERUSER_NAME}', '${SUPERUSER_EMAIL}', '${SUPERUSER_PASSWORD}')" | python /opt/netbox/netbox/manage.py shell
# copy static files
/opt/netbox/netbox/manage.py collectstatic --no-input
# start unicorn
gunicorn --log-level debug --debug --error-logfile /dev/stderr --log-file /dev/stdout -c /opt/netbox/gunicorn_config.py netbox.wsgi

View File

@@ -1,5 +0,0 @@
command = '/usr/bin/gunicorn'
pythonpath = '/opt/netbox/netbox'
bind = '0.0.0.0:8001'
workers = 3
user = 'root'

View File

@@ -1,35 +0,0 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
server_tokens off;
server {
listen 80;
server_name localhost;
access_log off;
location /static/ {
alias /opt/netbox/netbox/static/;
}
location / {
proxy_pass http://netbox:8001;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
}
}

View File

@@ -1,19 +0,0 @@
# API Integration
NetBox features a read-only REST API which can be used to integrate it with
other applications.
In the future, both read and write actions will be available via the API.
## Clients
The easiest way to start integrating your applications with NetBox is to make
use of an API client. If you build or discover an API client that is not part
of this list, please send a pull request!
- **Go**: [github.com/digitalocean/go-netbox](https://github.com/digitalocean/go-netbox)
## Documentation
If you wish to build a new API client or simply explore the NetBox API,
Swagger documentation can be found at the URL `/api/docs/` on a NetBox server.

View File

@@ -0,0 +1,48 @@
The NetBox API employs token-based authentication. For convenience, cookie authentication can also be used when navigating the browsable API.
# Tokens
A token is a unique identifier that identifies a user to the API. Each user in NetBox may have one or more tokens which he or she can use to authenticate to the API. To create a token, navigate to the API tokens page at `/user/api-tokens/`.
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
By default, a token can be used for all operations available via the API. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
Additionally, a token can be set to expire at a specific time. This can be useful if an external client needs to be granted temporary access to NetBox.
# Authenticating to the API
By default, read operations will be available without authentication. In this case, a token may be included in the request, but is not necessary.
```
$ curl -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/
{
"count": 10,
"next": null,
"previous": null,
"results": [...]
}
```
However, if the [`LOGIN_REQUIRED`](../configuration/optional-settings/#login_required) configuration setting has been set to `True`, all requests must be authenticated.
```
$ curl -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/
{
"detail": "Authentication credentials were not provided."
}
```
To authenticate to the API, set the HTTP `Authorization` header to the string `Token ` (note the trailing space) followed by the token key.
```
$ curl -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/
{
"count": 10,
"next": null,
"previous": null,
"results": [...]
}
```
Additionally, the browsable interface to the API (which can be seen by navigating to the API root `/api/` in a web browser) will attempt to authenticate requests using the same cookie that the normal NetBox front end uses. Thus, if you have logged into NetBox, you will be logged into the browsable API as well.

147
docs/api/examples.md Normal file
View File

@@ -0,0 +1,147 @@
# API Examples
Supported HTTP methods:
* `GET`: Retrieve an object or list of objects
* `POST`: Create a new object
* `PUT`: Update an existing object, all mandatory fields must be specified
* `PATCH`: Updates an existing object, only specifying the field to be changed
* `DELETE`: Delete an existing object
To authenticate a request, attach your token in an `Authorization` header:
```
curl -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0"
```
### Retrieving a list of sites
Send a `GET` request to the object list endpoint. The response contains a paginated list of JSON objects.
```
$ curl -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/
{
"count": 14,
"next": null,
"previous": null,
"results": [
{
"id": 6,
"name": "Corporate HQ",
"slug": "corporate-hq",
"region": null,
"tenant": null,
"facility": "",
"asn": null,
"physical_address": "742 Evergreen Terrace, Springfield, USA",
"shipping_address": "",
"contact_name": "",
"contact_phone": "",
"contact_email": "",
"comments": "",
"custom_fields": {},
"count_prefixes": 108,
"count_vlans": 46,
"count_racks": 8,
"count_devices": 254,
"count_circuits": 6
},
...
]
}
```
### Retrieving a single site by ID
Send a `GET` request to the object detail endpoint. The response contains a single JSON object.
```
$ curl -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/6/
{
"id": 6,
"name": "Corporate HQ",
"slug": "corporate-hq",
"region": null,
"tenant": null,
"facility": "",
"asn": null,
"physical_address": "742 Evergreen Terrace, Springfield, USA",
"shipping_address": "",
"contact_name": "",
"contact_phone": "",
"contact_email": "",
"comments": "",
"custom_fields": {},
"count_prefixes": 108,
"count_vlans": 46,
"count_racks": 8,
"count_devices": 254,
"count_circuits": 6
}
```
### Creating a new site
Send a `POST` request to the site list endpoint with token authentication and JSON-formatted data. Only mandatory fields are required. This example includes one non required field, "region."
```
$ curl -X POST -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/ --data '{"name": "My New Site", "slug": "my-new-site", "region": 5}'
{
"id": 16,
"name": "My New Site",
"slug": "my-new-site",
"region": 5,
"tenant": null,
"facility": "",
"asn": null,
"physical_address": "",
"shipping_address": "",
"contact_name": "",
"contact_phone": "",
"contact_email": "",
"comments": ""
}
```
Note that in this example we are creating a site bound to a region with the ID of 5. For write API actions (`POST`, `PUT`, and `PATCH`) the integer ID value is used for `ForeignKey` (related model) relationships, instead of the nested representation that is used in the `GET` (list) action.
### Modify an existing site
Make an authenticated `PUT` request to the site detail endpoint. As with a create (`POST`) request, all mandatory fields must be included.
```
$ curl -X PUT -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/16/ --data '{"name": "Renamed Site", "slug": "renamed-site"}'
```
### Modify an object by changing a field
Make an authenticated `PATCH` request to the device endpoint. With `PATCH`, unlike `POST` and `PUT`, we only specify the field that is being changed. In this example, we add a serial number to a device.
```
$ curl -X PATCH -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/devices/2549/ --data '{"serial": "FTX1123A090"}'
```
### Delete an existing site
Send an authenticated `DELETE` request to the site detail endpoint.
```
$ curl -v -X DELETE -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/16/
* Connected to localhost (127.0.0.1) port 8000 (#0)
> DELETE /api/dcim/sites/16/ HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8000
> Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0
> Content-Type: application/json
> Accept: application/json; indent=4
>
* HTTP 1.0, assume close after body
< HTTP/1.0 204 No Content
< Date: Mon, 20 Mar 2017 16:13:08 GMT
< Server: WSGIServer/0.1 Python/2.7.6
< Vary: Accept, Cookie
< X-Frame-Options: SAMEORIGIN
< Allow: GET, PUT, PATCH, DELETE, OPTIONS
<
* Closing connection 0
```
The response to a successful `DELETE` request will have code 204 (No Content); the body of the response will be empty.

208
docs/api/overview.md Normal file
View File

@@ -0,0 +1,208 @@
NetBox v2.0 and later includes a full-featured REST API that allows its data model to be read and manipulated externally.
# What is a REST API?
REST stands for [representational state transfer](https://en.wikipedia.org/wiki/Representational_state_transfer). It's a particular type of API which employs HTTP to create, retrieve, update, and delete objects from a database. (This set of operations is commonly referred to as CRUD.) Each type of operation is associated with a particular HTTP verb:
* `GET`: Retrieve an object or list of objects
* `POST`: Create an object
* `PUT` / `PATCH`: Modify an existing object. `PUT` requires all mandatory fields to be specified, while `PATCH` only expects the field that is being modified to be specified.
* `DELETE`: Delete an existing object
The NetBox API represents all objects in [JavaScript Object Notation (JSON)](http://www.json.org/). This makes it very easy to interact with NetBox data on the command line with common tools. For example, we can request an IP address from NetBox and output the JSON using `curl` and `jq`. (Piping the output through `jq` isn't strictly required but makes it much easier to read.)
```
$ curl -s http://localhost:8000/api/ipam/ip-addresses/2954/ | jq '.'
{
"custom_fields": {},
"nat_outside": null,
"nat_inside": null,
"description": "An example IP address",
"id": 2954,
"family": 4,
"address": "5.101.108.132/26",
"vrf": null,
"tenant": null,
"status": {
"label": "Active",
"value": 1
},
"role": null,
"interface": null
}
```
Each attribute of the NetBox object is expressed as a field in the dictionary. Fields may include their own nested objects, as in the case of the `status` field above. Every object includes a primary key named `id` which uniquely identifies it in the database.
# URL Hierarchy
NetBox's entire API is housed under the API root at `https://<hostname>/api/`. The URL structure is divided at the root level by application: circuits, DCIM, extras, IPAM, secrets, and tenancy. Within each application, each model has its own path. For example, the provider and circuit objects are located under the "circuits" application:
* /api/circuits/providers/
* /api/circuits/circuits/
Likewise, the site, rack, and device objects are located under the "DCIM" application:
* /api/dcim/sites/
* /api/dcim/racks/
* /api/dcim/devices/
The full hierarchy of available endpoints can be viewed by navigating to the API root in a web browser.
Each model generally has two views associated with it: a list view and a detail view. The list view is used to request a list of multiple objects or to create a new object. The detail view is used to retrieve, update, or delete an existing object. All objects are referenced by their numeric primary key (`id`).
* /api/dcim/devices/ - List devices or create a new device
* /api/dcim/devices/123/ - Retrieve, update, or delete the device with ID 123
Lists of objects can be filtered using a set of query parameters. For example, to find all interfaces belonging to the device with ID 123:
```
GET /api/dcim/interfaces/?device_id=123
```
# Serialization
The NetBox API employs three types of serializers to represent model data:
* Base serializer
* Nested serializer
* Writable serializer
The base serializer is used to represent the default view of a model. This includes all database table fields which comprise the model, and may include additional metadata. A base serializer includes relationships to parent objects, but **does not** include child objects. For example, the `VLANSerializer` includes a nested representation its parent VLANGroup (if any), but does not include any assigned Prefixes.
```
{
"id": 1048,
"site": {
"id": 7,
"url": "http://localhost:8000/api/dcim/sites/7/",
"name": "Corporate HQ",
"slug": "corporate-hq"
},
"group": {
"id": 4,
"url": "http://localhost:8000/api/ipam/vlan-groups/4/",
"name": "Production",
"slug": "production"
},
"vid": 101,
"name": "Users-Floor1",
"tenant": null,
"status": {
"value": 1,
"label": "Active"
},
"role": {
"id": 9,
"url": "http://localhost:8000/api/ipam/roles/9/",
"name": "User Access",
"slug": "user-access"
},
"description": "",
"display_name": "101 (Users-Floor1)",
"custom_fields": {}
}
```
Related objects (e.g. `ForeignKey` fields) are represented using a nested serializer. A nested serializer provides a minimal representation of an object, including only its URL and enough information to construct its name. When performing write api actions (`POST`, `PUT`, and `PATCH`), any `ForeignKey` relationships do not use the nested serializer, instead you will pass just the integer ID of the related model.
When a base serializer includes one or more nested serializers, the hierarchical structure precludes it from being used for write operations. Thus, a flat representation of an object may be provided using a writable serializer. This serializer includes only raw database values and is not typically used for retrieval, except as part of the response to the creation or updating of an object.
```
{
"id": 1201,
"site": 7,
"group": 4,
"vid": 102,
"name": "Users-Floor2",
"tenant": null,
"status": 1,
"role": 9,
"description": ""
}
```
## Static Choice Fields
Some model fields, such as the `status` field in the above example, utilize static integers corresponding to static choices. The available choices can be retrieved from the read-only `_choices` endpoint within each app. A specific `model:field` tuple may optionally be specified in the URL.
Each choice includes a human-friendly label and its corresponding numeric value. For example, `GET /api/ipam/_choices/prefix:status/` will return:
```
[
{
"value": 0,
"label": "Container"
},
{
"value": 1,
"label": "Active"
},
{
"value": 2,
"label": "Reserved"
},
{
"value": 3,
"label": "Deprecated"
}
]
```
Thus, to set a prefix's status to "Reserved," it would be assigned the integer `2`.
A request for `GET /api/ipam/_choices/` will return choices for _all_ fields belonging to models within the IPAM app.
# Pagination
API responses which contain a list of objects (for example, a request to `/api/dcim/devices/`) will be paginated to avoid unnecessary overhead. The root JSON object will contain the following attributes:
* `count`: The total count of all objects matching the query
* `next`: A hyperlink to the next page of results (if applicable)
* `previous`: A hyperlink to the previous page of results (if applicable)
* `results`: The list of returned objects
Here is an example of a paginated response:
```
HTTP 200 OK
Allow: GET, POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
"count": 2861,
"next": "http://localhost:8000/api/dcim/devices/?limit=50&offset=50",
"previous": null,
"results": [
{
"id": 123,
"name": "DeviceName123",
...
},
...
]
}
```
The default page size derives from the [`PAGINATE_COUNT`](../configuration/optional-settings/#paginate_count) configuration setting, which defaults to 50. However, this can be overridden per request by specifying the desired `offset` and `limit` query parameters. For example, if you wish to retrieve a hundred devices at a time, you would make a request for:
```
http://localhost:8000/api/dcim/devices/?limit=100
```
The response will return devices 1 through 100. The URL provided in the `next` attribute of the response will return devices 101 through 200:
```
{
"count": 2861,
"next": "http://localhost:8000/api/dcim/devices/?limit=100&offset=100",
"previous": null,
"results": [...]
}
```
The maximum number of objects that can be returned is limited by the [`MAX_PAGE_SIZE`](../configuration/optional-settings/#max_page_size) setting, which is 1000 by default. Setting this to `0` or `None` will remove the maximum limit. An API consumer can then pass `?limit=0` to retrieve _all_ matching objects with a single request.
!!! warning
Disabling the page size limit introduces a potential for very resource-intensive requests, since one API request can effectively retrieve an entire table from the database.

View File

@@ -0,0 +1,136 @@
As with most other objects, the NetBox API can be used to create, modify, and delete secrets. However, additional steps are needed to encrypt or decrypt secret data.
# Generating a Session Key
In order to encrypt or decrypt secret data, a session key must be attached to the API request. To generate a session key, send an authenticated request to the `/api/secrets/get-session-key/` endpoint with the private RSA key which matches your [UserKey](../data-model/secrets/#user-keys). The private key must be POSTed with the name `private_key`.
```
$ curl -X POST http://localhost:8000/api/secrets/get-session-key/ \
-H "Authorization: Token c639d619ecbeb1f3055c4141ba6870e20572edd7" \
-H "Accept: application/json; indent=4" \
--data-urlencode "private_key@<filename>"
{
"session_key": "dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk="
}
```
!!! note
To read the private key from a file, use the convention above. Alternatively, the private key can be read from an environment variable using `--data-urlencode "private_key=$PRIVATE_KEY"`.
The request uses your private key to unlock your stored copy of the master key and generate a session key which can be attached in the `X-Session-Key` header of future API requests.
# Retrieving Secrets
A session key is not needed to retrieve unencrypted secrets: The secret is returned like any normal object with its `plaintext` field set to null.
```
$ curl http://localhost:8000/api/secrets/secrets/2587/ \
-H "Authorization: Token c639d619ecbeb1f3055c4141ba6870e20572edd7" \
-H "Accept: application/json; indent=4"
{
"id": 2587,
"device": {
"id": 1827,
"url": "http://localhost:8000/api/dcim/devices/1827/",
"name": "MyTestDevice",
"display_name": "MyTestDevice"
},
"role": {
"id": 1,
"url": "http://localhost:8000/api/secrets/secret-roles/1/",
"name": "Login Credentials",
"slug": "login-creds"
},
"name": "admin",
"plaintext": null,
"hash": "pbkdf2_sha256$1000$G6mMFe4FetZQ$f+0itZbAoUqW5pd8+NH8W5rdp/2QNLIBb+LGdt4OSKA=",
"created": "2017-03-21",
"last_updated": "2017-03-21T19:28:44.265582Z"
}
```
To decrypt a secret, we must include our session key in the `X-Session-Key` header:
```
$ curl http://localhost:8000/api/secrets/secrets/2587/ \
-H "Authorization: Token c639d619ecbeb1f3055c4141ba6870e20572edd7" \
-H "Accept: application/json; indent=4" \
-H "X-Session-Key: dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk="
{
"id": 2587,
"device": {
"id": 1827,
"url": "http://localhost:8000/api/dcim/devices/1827/",
"name": "MyTestDevice",
"display_name": "MyTestDevice"
},
"role": {
"id": 1,
"url": "http://localhost:8000/api/secrets/secret-roles/1/",
"name": "Login Credentials",
"slug": "login-creds"
},
"name": "admin",
"plaintext": "foobar",
"hash": "pbkdf2_sha256$1000$G6mMFe4FetZQ$f+0itZbAoUqW5pd8+NH8W5rdp/2QNLIBb+LGdt4OSKA=",
"created": "2017-03-21",
"last_updated": "2017-03-21T19:28:44.265582Z"
}
```
Lists of secrets can be decrypted in this manner as well:
```
$ curl http://localhost:8000/api/secrets/secrets/?limit=3 \
-H "Authorization: Token c639d619ecbeb1f3055c4141ba6870e20572edd7" \
-H "Accept: application/json; indent=4" \
-H "X-Session-Key: dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk="
{
"count": 3482,
"next": "http://localhost:8000/api/secrets/secrets/?limit=3&offset=3",
"previous": null,
"results": [
{
"id": 2587,
...
"plaintext": "foobar",
...
},
{
"id": 2588,
...
"plaintext": "MyP@ssw0rd!",
...
},
{
"id": 2589,
...
"plaintext": "AnotherSecret!",
...
},
]
}
```
# Creating Secrets
Session keys are also used to decrypt new or modified secrets. This is done by setting the `plaintext` field of the submitted object:
```
$ curl -X POST http://localhost:8000/api/secrets/secrets/ \
-H "Content-Type: application/json" \
-H "Authorization: Token c639d619ecbeb1f3055c4141ba6870e20572edd7" \
-H "Accept: application/json; indent=4" \
-H "X-Session-Key: dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk=" \
--data '{"device": 1827, "role": 1, "name": "backup", "plaintext": "Drowssap1"}'
{
"id": 2590,
"device": 1827,
"role": 1,
"name": "backup",
"plaintext": "Drowssap1"
}
```
!!! note
Don't forget to include the `Content-Type: application/json` header when making a POST request.

View File

@@ -17,7 +17,7 @@ ADMINS = [
## BANNER_BOTTOM
Setting these variables will display content in a banner at the top and/or bottom of the page, respectively. To replicate the content of the top banner in the bottom banner, set:
Setting these variables will display content in a banner at the top and/or bottom of the page, respectively. HTML is allowed. To replicate the content of the top banner in the bottom banner, set:
```
BANNER_TOP = 'Your banner text'
@@ -26,6 +26,40 @@ BANNER_BOTTOM = BANNER_TOP
---
## BANNER_LOGIN
The value of this variable will be displayed on the login page above the login form. HTML is allowed.
---
## BASE_PATH
Default: None
The base URL path to use when accessing NetBox. Do not include the scheme or domain name. For example, if installed at http://example.com/netbox/, set:
```
BASE_PATH = 'netbox/'
```
---
## CORS_ORIGIN_ALLOW_ALL
Default: False
If True, cross-origin resource sharing (CORS) requests will be accepted from all origins. If False, a whitelist will be used (see below).
---
## CORS_ORIGIN_WHITELIST
## CORS_ORIGIN_REGEX_WHITELIST
These settings specify a list of origins that are authorized to make cross-site API requests. Use `CORS_ORIGIN_WHITELIST` to define a list of exact hostnames, or `CORS_ORIGIN_REGEX_WHITELIST` to define a set of regular expressions. (These settings have no effect if `CORS_ORIGIN_ALLOW_ALL` is True.)
---
## DEBUG
Default: False
@@ -55,6 +89,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
@@ -71,11 +133,61 @@ Setting this to True will display a "maintenance mode" banner at the top of ever
---
## NETBOX_USERNAME
## MAX_PAGE_SIZE
## NETBOX_PASSWORD
Default: 1000
If provided, NetBox will use these credentials to authenticate against devices when collecting data.
An API consumer can request an arbitrary number of objects by appending the "limit" parameter to the URL (e.g. `?limit=1000`). This setting defines the maximum limit. Setting it to `0` or `None` will allow an API consumer to request all objects by specifying `?limit=0`.
---
## MEDIA_ROOT
Default: $BASE_DIR/netbox/media/
The file path to the location where media files (such as image attachments) are stored. By default, this is the `netbox/media/` directory within the base NetBox installation path.
---
## NAPALM_USERNAME
## NAPALM_PASSWORD
NetBox will use these credentials when authenticating to remote devices via the [NAPALM library](https://napalm-automation.net/), if installed. Both parameters are optional.
Note: If SSH public key authentication has been set up for the system account under which NetBox runs, these parameters are not needed.
---
## NAPALM_ARGS
A dictionary of optional arguments to pass to NAPALM when instantiating a network driver. See the NAPALM documentation for a [complete list of optional arguments](http://napalm.readthedocs.io/en/latest/support/#optional-arguments). An example:
```
NAPALM_ARGS = {
'api_key': '472071a93b60a1bd1fafb401d9f8ef41',
'port': 2222,
}
```
Note: Some platforms (e.g. Cisco IOS) require an argument named `secret` to be passed in addition to the normal password. If desired, you can use the configured `NAPALM_PASSWORD` as the value for this argument:
```
NAPALM_USERNAME = 'username'
NAPALM_PASSWORD = 'MySecretPassword'
NAPALM_ARGS = {
'secret': NAPALM_PASSWORD,
# Include any additional args here
}
```
---
## NAPALM_TIMEOUT
Default: 30 seconds
The amount of time (in seconds) to wait for NAPALM to connect to a device.
---
@@ -95,6 +207,14 @@ When determining the primary IP address for a device, IPv6 is preferred over IPv
---
## REPORTS_ROOT
Default: $BASE_DIR/netbox/reports/
The file path to the location where custom reports will be kept. By default, this is the `netbox/reports/` directory within the base NetBox installation path.
---
## TIME_ZONE
Default: UTC

View File

@@ -2,31 +2,32 @@ The circuits component of NetBox deals with the management of long-haul Internet
# Providers
A provider is any entity which provides some form of connectivity. This obviously includes carriers which offer Internet and private transit service. However, it might also include Internet exchange (IX) points and even organizations with whom you peer directly.
A provider is any entity which provides some form of connectivity. While this obviously includes carriers which offer Internet and private transit service, it might also include Internet exchange (IX) points and even organizations with whom you peer directly.
Each provider may be assigned an autonomous system number (ASN) for reference. Each provider can also be assigned account and contact information, as well as miscellaneous comments.
Each provider may be assigned an autonomous system number (ASN), an account number, and contact information.
---
# Circuits
A circuit represents a single physical data link connecting two endpoints. Each circuit belongs to a provider and must be assigned circuit ID which is unique to that provider. Each circuit must also be assigned to a site, and may optionally be connected to a specific interface on a specific device within that site.
NetBox also tracks miscellaneous circuit attributes (most of which are optional), including:
* Date of installation
* Port speed
* Commit rate
* Cross-connect ID
* Patch panel information
A circuit represents a single physical data link connecting two endpoints. Each circuit belongs to a provider and must be assigned a circuit ID which is unique to that provider.
### Circuit Types
Circuits can be classified by type. For example:
Circuits are classified by type. For example, you might define circuit types for:
* Internet transit
* Out-of-band connectivity
* Peering
* Private backhaul
Each circuit must be assigned exactly one circuit type.
Circuit types are fully customizable.
### Circuit Terminations
A circuit may have one or two terminations, annotated as the "A" and "Z" sides of the circuit. A single-termination circuit can be used when you don't know (or care) about the far end of a circuit (for example, an Internet access circuit which connects to a transit provider). A dual-termination circuit is useful for tracking circuits which connect two sites.
Each circuit termination is tied to a site, and optionally to a specific device and interface within that site. Each termination can be assigned a separate downstream and upstream speed independent from one another. Fields are also available to track cross-connect and patch panel details.
!!! note
A circuit represents a physical link, and cannot have more than two endpoints. When modeling a multi-point topology, each leg of the topology must be defined as a discrete circuit.

View File

@@ -2,55 +2,72 @@ Data center infrastructure management (DCIM) entails all physical assets: sites,
# Sites
How you define sites will depend on the nature of your organization, but typically a site will equate a building or campus. For example, a chain of banks might create a site to represent each of its branches, a site for its corporate headquarters, and two additional sites for its presence in two colocation facilities.
How you choose to use sites will depend on the nature of your organization, but typically a site will equate to a building or campus. For example, a chain of banks might create a site to represent each of its branches, a site for its corporate headquarters, and two additional sites for its presence in two colocation facilities.
Sites can be assigned an optional facility ID to identify the actual facility housing colocated equipment.
Sites can be assigned an optional facility ID to identify the actual facility housing colocated equipment, and an Autonomous System (AS) number.
### Regions
Sites can be arranged geographically using regions. A region might represent a continent, country, city, campus, or other area depending on your use case. Regions can be nested recursively to construct a hierarchy. For example, you might define several country regions, and within each of those several state or city regions to which sites are assigned.
---
# Racks
Within each site exist one or more racks. Each rack within NetBox represents a physical two- or four-post equipment rack in which equipment is mounted. Rack height is measured in *rack units *(U); most racks are between 42U and 48U, but NetBox allows you to define racks of any height. Each rack has two faces (front and rear) on which devices can be mounted.
The rack model represents a physical two- or four-post equipment rack in which equipment is mounted. Each rack is assigned to a site. Rack height is measured in *rack units* (U); racks are commonly between 42U and 48U, but NetBox allows you to define racks of arbitrary height. Each rack has two faces (front and rear) on which devices can be mounted.
Each rack is assigned a name and (optionally) a separate facility ID. This is helpful when leasing space in a data center your organization does not own: The facility will often assign a seemingly arbitrary ID to a rack (for example, M204.313) whereas internally you refer to is simply as "R113." The facility ID can alternatively be used to store a rack's serial number.
Each rack is assigned a name and (optionally) a separate facility ID. This is helpful when leasing space in a data center your organization does not own: The facility will often assign a seemingly arbitrary ID to a rack (for example, "M204.313") whereas internally you refer to is simply as "R113." The facility ID can alternatively be used to store a rack's serial number.
The available rack types include 2- and 4-post frames, 4-post cabinet, and wall-mounted frame and cabinet. Rail-to-rail width may be 19 or 23 inches.
### Rack Groups
Racks can be arranged into groups. As with sites, how you choose to designate rack groups will depend on the nature of your organization. For example, if each site is a campus, each group might be a building. If each site is a building, each rack group might be a floor or room.
Racks can be arranged into groups. As with sites, how you choose to designate rack groups will depend on the nature of your organization. For example, if each site represents a campus, each group might represent a building within a campus. If each site represents a building, each rack group might equate to a floor or room.
Each group is assigned to a parent site for easy navigation. Hierarchical recursion of rack groups is not currently supported.
Each group is assigned to a parent site for easy navigation. Hierarchical recursion of rack groups is not supported.
### Rack Roles
Each rack can optionally be assigned a functional role. For example, you might designate a rack for compute or storage resources, or to house colocated customer devices. Rack roles are fully customizable.
### Rack Space Reservations
Users can reserve units within a rack for future use. Multiple non-contiguous rack units can be associated with a single reservation (but reservations cannot span multiple racks).
---
# Device Types
A device type represents a particular manufacturer and model of equipment. Device types describe the physical attributes of a device (rack height and depth), its class (e.g. console server, PDU, etc.), and its individual components (console, power, and data).
A device type represents a particular hardware model that exists in the real world. Device types describe the physical attributes of a device (rack height and depth), its class (e.g. console server, PDU, etc.), and its individual components (console, power, and data).
Device types are instantiated as devices installed within racks. For example, you might define a device type to represent a Juniper EX4300-48T network switch with 48 Ethernet interfaces. You can then create multiple devices of this type named "switch1," "switch2," and so on. Each device will inherit the components (such as interfaces) of its device type.
### Manufacturers
Each device type belongs to one manufacturer; e.g. Cisco, Opengear, or APC. Manufacturers are used to group different models of device.
Each device type belongs to one manufacturer; e.g. Cisco, Opengear, or APC. The model number of a device type must be unique to its manufacturer.
### Component Templates
Each device type is assigned a number of component templates which describe the console, power, and data ports a device has. These are:
Each device type is assigned a number of component templates which define the physical interfaces a device has. These are:
* Console port templates
* Console server port templates
* Power port templates
* Power outlet templates
* Interface templates
* Device bay templates
* Console ports
* Console server ports
* Power ports
* Power outlets
* Interfaces
* Device bays
Whenever a new device is created, it is automatically assigned console, power, and interface components per the templates assigned to its device type. For example, suppose your network employs Juniper EX4300-48T switches. You would create a device type with a model name "EX4300-48T" and assign it to the manufacturer "Juniper." You might then also create the following templates for it:
Whenever a new device is created, it is automatically assigned components per the templates assigned to its device type. For example, a Juniper EX4300-48T device type might have the following component templates:
* One template for a console port ("Console")
* Two templates for power ports ("PSU0" and "PSU1")
* 48 templates for 1GE interfaces ("ge-0/0/0" through "ge-0/0/47")
* Four templates for 10GE interfaces ("xe-0/2/0" through "xe-0/2/3")
Once you've done this, every new device that you create as an instance of this type will automatically be assigned each of the components listed above.
Once component templates have been created, every new device that you create as an instance of this type will automatically be assigned each of the components listed above.
Note that assignment of components from templates occurs only at the time of device creation: If you modify the templates of a device type, it will not affect devices which have already been created. However, you always have the option of adding, modifying, or deleting components of existing devices individually.
!!! note
Assignment of components from templates occurs only at the time of device creation. If you modify the templates of a device type, it will not affect devices which have already been created. However, you always have the option of adding, modifying, or deleting components of existing devices individually.
---
@@ -58,23 +75,26 @@ Note that assignment of components from templates occurs only at the time of dev
Every piece of hardware which is installed within a rack exists in NetBox as a device. Devices are measured in rack units (U) and depth. 0U devices which can be installed in a rack but don't consume vertical rack space (such as a vertically-mounted power distribution unit) can also be defined.
When assigning a multi-U device to a rack, it is considered to be mounted in the lowest-numbered rack unit which it occupies. For example, a 3U device which occupies U8 through U10 shows as being mounted in U8.
When assigning a multi-U device to a rack, it is considered to be mounted in the lowest-numbered rack unit which it occupies. For example, a 3U device which occupies U8 through U10 shows as being mounted in U8. This logic applies to racks with both ascending and descending unit numbering.
A device is said to be "full depth" if its installation on one rack face prevents the installation of any other device on the opposite face within the same rack unit(s). This could be either because the device is physically too deep to allow a device behind it, or because the installation of an opposing device would impede air flow.
### Roles
NetBox allows for the definition of arbitrary device roles by which devices can be organized. For example, you might create roles for core switches, distribution switches, and access switches. In the interest of simplicity, device can only belong to one device role.
NetBox allows for the definition of arbitrary device roles by which devices can be organized. For example, you might create roles for core switches, distribution switches, and access switches. In the interest of simplicity, a device can belong to only one role.
### Platforms
A device's platform is used to denote the type of software running on it. This can be helpful when it is necessary to distinguish between, for instance, different feature sets. Note that two devices of same type may be assigned different platforms: for example, one Juniper MX240 running Junos 14 and another running Junos 15.
The assignment of platforms to devices is an entirely optional feature, and may be disregarded if not desired.
The assignment of platforms to devices is an optional feature, and may be disregarded if not desired.
### Modules
### Inventory Items
A device can be assigned modules which represent internal components. Currently, these are used merely for inventory tracking, although future development might see their functionality expand.
Inventory items represent hardware components installed within a device, such as a power supply or CPU. Currently, these are used merely for inventory tracking, although future development might see their functionality expand. Each item can optionally be assigned a manufacturer.
!!! note
Prior to version 2.0, inventory items were called modules.
### Components
@@ -87,10 +107,16 @@ There are six types of device components which comprise all of the interconnecti
* Interfaces
* Device bays
Console ports connect only to console server ports, and power ports connect only to power outlets. Interfaces connect to one another in a symmetric manner: If interface A connects to interface B, interface B therefore connects to interface A. (The relationship between two interfaces is actually represented in the database by an InterfaceConnection object, but this is transparent to the user.)
Console ports connect only to console server ports, and power ports connect only to power outlets. Interfaces connect to one another in a symmetric manner: If interface A connects to interface B, interface B therefore connects to interface A. (The relationship between two interfaces is actually represented in the database by an InterfaceConnection object, but this is transparent to the user.) Each type of connection can be classified as either *planned* or *connected*. This allows for easily denoting connections which have not yet been installed.
Each type of connection can be classified as either *planned* or *connected*. This allows for easily denoting connections which have not yet been installed. In addition to a connecting peer, interfaces are also assigned a form factor and may be designated as management-only (for out-of-band management). Interfaces may also be assigned a short description.
Each interface is a assigned a form factor denoting its physical properties. Two special form factors exist: the "virtual" form factor can be used to designate logical interfaces (such as SVIs), and the "LAG" form factor can be used to desinate link aggregation groups to which physical interfaces can be assigned. Each interface can also be designated as management-only (for out-of-band management) and assigned a short description.
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear on rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.
Note that child devices differ from modules in that they are still treated as independent devices, with their own console/power/data components, modules, and IP addresses. Modules, on the other hand, are parts within a device, such as a hard disk or power supply.
---
# Virtual Chassis
A virtual chassis represents a set of devices which share a single control plane: for example, a stack of switches which are managed as a single device. Each device in the virtual chassis is assigned a position and (optionally) a priority. Exactly one device is designated the virtual chassis master: This device will typically be assigned a name, secrets, services, and other attributes related to its management.
It's important to recognize the distinction between a virtual chassis and a chassis-based device. For instance, a virtual chassis is not used to model a chassis switch with removable line cards such as the Juniper EX9208, as its line cards are _not_ physically separate devices capable of operating independently.

View File

@@ -1,4 +1,31 @@
This section entails features of NetBox which are not crucial to its primary functions, but that provide additional value.
This section entails features of NetBox which are not crucial to its primary functions, but provide additional value.
# Custom Fields
Each object in NetBox is represented in the database as a discrete table, and each attribute of an object exists as a column within its table. For example, sites are stored in the `dcim_site` table, which has columns named `name`, `facility`, `physical_address`, and so on. As new attributes are added to objects throughout the development of NetBox, tables are expanded to include new rows.
However, some users might want to associate with objects attributes that are somewhat esoteric in nature, and that would not make sense to include in the core NetBox database schema. For instance, suppose your organization needs to associate each device with a ticket number pointing to the support ticket that was opened to have it installed. This is certainly a legitimate use for NetBox, but it's perhaps not a common enough need to warrant expanding the internal data schema. Instead, you can create a custom field to hold this data.
Custom fields must be created through the admin UI under Extras > Custom Fields. To create a new custom field, select the object(s) to which you want it to apply, and the type of field it will be. NetBox supports six field types:
* Free-form text (up to 255 characters)
* Integer
* Boolean (true/false)
* Date
* URL
* Selection
Assign the field a name. This should be a simple database-friendly string, e.g. `tps_report`. You may optionally assign the field a human-friendly label (e.g. "TPS report") as well; the label will be displayed on forms. If a description is provided, it will appear beneath the field in a form.
Marking the field as required will require the user to provide a value for the field when creating a new object or when saving an existing object. A default value for the field may also be provided. Use "true" or "false" for boolean fields. (The default value has no effect for selection fields.)
When creating a selection field, you should create at least two choices. These choices will be arranged first by weight, with lower weights appearing higher in the list, and then alphabetically.
## Using Custom Fields
When a single object is edited, the form will include any custom fields which have been defined for the object type. These fields are included in the "Custom Fields" panel. On the backend, each custom field value is saved separately from the core object as an independent database call, so it's best to avoid adding too many custom fields per object.
When editing multiple objects, custom field values are saved in bulk. There is no significant difference in overhead when saving a custom field value for 100 objects versus one object. However, the bulk operation must be performed separately for each custom field.
# Export Templates
@@ -6,7 +33,17 @@ NetBox allows users to define custom templates that can be used when exporting o
Each export template is associated with a certain type of object. For instance, if you create an export template for VLANs, your custom template will appear under the "Export" button on the VLANs list.
Export templates are written in [Django's template language](https://docs.djangoproject.com/en/1.9/ref/templates/language/), which is very similar to Jinja2. The list of objects returned from the database is stored in the `queryset` variable. Typically, you'll want to iterate through this list using a for loop.
Export templates are written in [Django's template language](https://docs.djangoproject.com/en/1.9/ref/templates/language/), which is very similar to Jinja2. The list of objects returned from the database is stored in the `queryset` variable, which you'll typically want to iterate through using a `for` loop. Object properties can be access by name. For example:
```
{% for rack in queryset %}
Rack: {{ rack.name }}
Site: {{ rack.site.name }}
Height: {{ rack.u_height }}U
{% endfor %}
```
To access custom fields of an object within a template, use the `cf` attribute. For example, `{{ obj.cf.color }}` will return the value (if any) for a custom field named `color` on `obj`.
A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`.
@@ -15,10 +52,10 @@ A MIME type and file extension can optionally be defined for each export templat
Here's an example device export template that will generate a simple Nagios configuration from a list of devices.
```
{% for d in queryset %}{% if d.status and d.primary_ip %}define host{
{% for device in queryset %}{% if device.status and device.primary_ip %}define host{
use generic-switch
host_name {{ d.name }}
address {{ d.primary_ip.address.ip }}
host_name {{ device.name }}
address {{ device.primary_ip.address.ip }}
}
{% endif %}{% endfor %}
```
@@ -45,19 +82,35 @@ define host{
# Graphs
NetBox does not generate graphs itself. This feature allows you to embed contextual graphs from an external resources inside certain NetBox views. Each embedded graph must be defined with the following parameters:
NetBox does not have the ability to generate graphs natively, but this feature allows you to embed contextual graphs from an external resources (such as a monitoring system) inside the site, provider, and interface views. Each embedded graph must be defined with the following parameters:
* **Type:** Interface, provider, or site. This determines where the graph will be displayed.
* **Type:** Site, provider, or interface. This determines in which view the graph will be displayed.
* **Weight:** Determines the order in which graphs are displayed (lower weights are displayed first). Graphs with equal weights will be ordered alphabetically by name.
* **Name:** The title to display above the graph.
* **Source URL:** The source of the image to be embedded. The associated object will be available as a template variable named `obj`.
* **Link URL (optional):** A URL to which the graph will be linked. The associated object will be available as a template variable named `obj`.
## Examples
You only need to define one graph object for each graph you want to include when viewing an object. For example, if you want to include a graph of traffic through an interface over the past five minutes, your graph source might looks like this:
```
https://my.nms.local/graphs/?node={{ obj.device.name }}&interface={{ obj.name }}&duration=5m
```
You can define several graphs to provide multiple contexts when viewing an object. For example:
```
https://my.nms.local/graphs/?type=throughput&node={{ obj.device.name }}&interface={{ obj.name }}&duration=60m
https://my.nms.local/graphs/?type=throughput&node={{ obj.device.name }}&interface={{ obj.name }}&duration=24h
https://my.nms.local/graphs/?type=errors&node={{ obj.device.name }}&interface={{ obj.name }}&duration=60m
```
# Topology Maps
NetBox can generate simple topology maps from the physical network connections recorded in its database. First, you'll need to create a topology map definition under the admin UI at Extras > Topology Maps.
Each topology map is associated with a site. A site can have multiple topology maps, which might each illustrate a different aspect of its infrastructure (for example, production versus backend connectivity).
Each topology map is associated with a site. A site can have multiple topology maps, which might each illustrate a different aspect of its infrastructure (for example, production versus backend infrastructure).
To define the scope of a topology map, decide which devices you want to include. The map will only include interface connections with both points terminated on an included device. Specify the devices to include in the **device patterns** field by entering a list of [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) matching device names. For example, if you wanted to include "mgmt-switch1" through "mgmt-switch99", you might use the regex `mgmt-switch\d+`.
@@ -66,7 +119,14 @@ Each line of the **device patterns** field represents a hierarchical layer withi
```
core-switch-[abcd]
dist-switch\d
access-switch\d+,oob-switch\d+
access-switch\d+;oob-switch\d+
```
Note that you can combine multiple regexes onto one line using commas. (Commas can only be used for separating regexes; they will not be processed as part of a regex.) The order in which regexes are listed on a line is significant: devices matching the first regex will be rendered first, and subsequent groups will be rendered to the right of those.
Note that you can combine multiple regexes onto one line using semicolons. The order in which regexes are listed on a line is significant: devices matching the first regex will be rendered first, and subsequent groups will be rendered to the right of those.
# Image Attachments
Certain objects within NetBox (namely sites, racks, and devices) can have photos or other images attached to them. (Note that _only_ image files are supported.) Each attachment may optionally be assigned a name; if omitted, the attachment will be represented by its file name.
!!! note
If you experience a server error while attempting to upload an image attachment, verify that the system user NetBox runs as has write permission to the media root directory (`netbox/media/`).

View File

@@ -6,11 +6,14 @@ A VRF object in NetBox represents a virtual routing and forwarding (VRF) domain
Each VRF is assigned a name and a unique route distinguisher (RD). VRFs are an optional feature of NetBox: Any IP prefix or address not assigned to a VRF is said to belong to the "global" table.
!!! note
By default, NetBox allows for overlapping IP space both in the global table and within each VRF. Unique space enforcement can be toggled per-VRF as well as in the global table using the `ENFORCE_GLOBAL_UNIQUE` configuration setting.
---
# Aggregates
IPv4 address space is organized as a hierarchy, with more-specific (smaller) prefix arranged as child nodes under less-specific (larger) prefixes. For example:
IP address space is organized as a hierarchy, with more-specific (smaller) prefixes arranged as child nodes under less-specific (larger) prefixes. For example:
* 10.0.0.0/8
* 10.1.0.0/16
@@ -18,23 +21,23 @@ IPv4 address space is organized as a hierarchy, with more-specific (smaller) pre
The root of the IPv4 hierarchy is 0.0.0.0/0, which encompasses all possible IPv4 addresses (and similarly, ::/0 for IPv6). However, even the largest organizations use only a small fraction of the global address space. Therefore, it makes sense to track in NetBox only the address space which is of interest to your organization.
Aggregates serve as arbitrary top-level nodes in the IP space hierarchy. They allow you to easily construct your IP scheme without any clutter of unused address space. For instance, most organizations utilize some portion of the RFC 1918 private IPv4 space. So, you might define three aggregates for this space:
Aggregates serve as arbitrary top-level nodes in the IP space hierarchy. They allow you to easily construct your IP scheme without any clutter of unused address space. For instance, most organizations utilize some portion of the private IPv4 space set aside in RFC 1918. So, you might define three aggregates for this space:
* 10.0.0.0/8
* 172.16.0.0/12
* 192.168.0.0/16
Additionally, you might define an aggregate for each large swath of public IPv4 space your organization uses. You'd also create aggregates for both globally routable and unique local IPv6 space.
Additionally, you might define an aggregate for each large swath of public IPv4 space your organization uses. You'd also create aggregates for both globally routable and unique local IPv6 space. (Most organizations will not have a need to track IPv6 link local space.)
Any prefixes you create in NetBox (discussed below) will be automatically organized under their respective aggregates. Any space within an aggregate which is not covered by an existing prefix will be annotated as available for allocation.
Prefixes you create in NetBox (discussed below) will be automatically organized under their respective aggregates. Any space within an aggregate which is not covered by an existing prefix will be annotated as available for allocation. Total utilization for each aggregate is displayed in the aggregates list.
Aggregates cannot overlap with one another; they can only exist in parallel. For instance, you cannot define both 10.0.0.0/8 and 10.16.0.0/16 as aggregates, because they overlap. 10.16.0.0/16 in this example would be created as a prefix.
Aggregates cannot overlap with one another; they can only exist in parallel. For instance, you cannot define both 10.0.0.0/8 and 10.16.0.0/16 as aggregates, because they overlap. 10.16.0.0/16 in this example would be created as a prefix and automatically grouped under 10.0.0.0/8.
### RIRs
Regional Internet Registries (RIRs) are responsible for the allocation of global address space. The five RIRs are ARIN, RIPE, APNIC, LACNIC, and AFRINIC. However, some address space has been set aside for private or internal use only, such as defined in RFCs 1918 and 6598. NetBox considers these RFCs as a sort of RIR as well; that is, an authority which "owns" certain address space.
Each aggregate must be assigned to one RIR. You are free to define whichever RIRs you choose (or create your own).
Each aggregate must be assigned to one RIR. You are free to define whichever RIRs you choose (or create your own). Each RIR can be annotated as representing only private space.
---
@@ -44,7 +47,7 @@ A prefix is an IPv4 or IPv6 network and mask expressed in CIDR notation (e.g. 19
Each prefix may be assigned to one VRF; prefixes not assigned to a VRF are assigned to the "global" table. Prefixes are also organized under their respective aggregates, irrespective of VRF assignment.
A prefix may optionally be assigned to one VLAN; a VLAN may have multiple prefixes assigned to it. This can be helpful is replicating real-world IP assignments. Each prefix may also be assigned a short description.
A prefix may optionally be assigned to one VLAN; a VLAN may have multiple prefixes assigned to it. Each prefix may also be assigned a short description.
### Statuses
@@ -52,7 +55,7 @@ Each prefix is assigned an operational status. This is one of the following:
* Container - A summary of child prefixes
* Active - Provisioned and in use
* Reserved - Earmarked for future use
* Reserved - Designated for future use
* Deprecated - No longer in use
### Roles
@@ -65,24 +68,32 @@ Whereas a status describes a prefix's operational state, a role describes its fu
* Lab
* Out-of-band
Role assignment is optional and you are free to create as many as you'd like.
Role assignment is optional and roles are fully customizable.
---
# IP Addresses
An IP address comprises a single address (either IPv4 or IPv6) and its mask. Its mask should match exactly how the IP address is configured on an interface in the real world.
An IP address comprises a single address (either IPv4 or IPv6) and its subnet mask. Its mask should match exactly how the IP address is configured on an interface in the real world.
Like prefixes, an IP address can optionally be assigned to a VRF (or it will appear in the "global" table). IP addresses are automatically organized under parent prefixes within their respective VRFs. Each IP address can also be assigned a short description.
Each IP address can optionally be assigned to a device's interface; an interface may have multiple IP addresses assigned to it. Further, each device may have one of its interface IPs designated as its primary IP address.
An IP address can be assigned to a device's interface; an interface may have multiple IP addresses assigned to it. Further, each device may have one of its interface IPs designated as its primary IP address (for both IPv4 and IPv6).
One IP address can be designated as the network address translation (NAT) IP address for exactly one other IP address. This is useful primarily is denoting the public address for a private internal IP. Tracking one-to-many NAT (or PAT) assignments is not currently supported.
One IP address can be designated as the network address translation (NAT) IP address for exactly one other IP address. This is useful primarily to denote the public address for a private internal IP. Tracking one-to-many NAT (or PAT) assignments is not supported.
---
# VLANs
A VLAN represents an isolated layer two domain, identified by a name and a numeric ID (1-4094). Note that while it is good practice, neither VLAN names nor IDs must be unique within a site. This is to accommodate the fact that many real-world network use less-than-optimal VLAN allocations and may have overlapping VLAN ID assignments in practice.
A VLAN represents an isolated layer two domain, identified by a name and a numeric ID (1-4094) as defined in [IEEE 802.1Q](https://en.wikipedia.org/wiki/IEEE_802.1Q). Each VLAN may be assigned to a site and/or VLAN group. Like prefixes, each VLAN is assigned an operational status and (optionally) a functional role, and may include a short description.
Like prefixes, each VLAN is assigned an operational status and (optionally) a functional role.
### VLAN Groups
VLAN groups can be employed for administrative organization within NetBox. Each VLAN within a group must have a unique ID and name. VLANs which are not assigned to a group may have overlapping names and IDs, including within a site.
---
# Services
A service represents a TCP or UDP service available on a device or virtual machine. Each service must be defined with a name, protocol, and port number; for example, "SSH (TCP/22)." A service may optionally be bound to one or more specific IP addresses belonging to its parent. (If no IP addresses are bound, the service is assumed to be reachable via any assigned IP address.)

View File

@@ -24,11 +24,11 @@ Roles are also used to control access to secrets. Each role is assigned an arbit
Each user within NetBox can associate his or her account with an RSA public key. If activated by an administrator, this user key will contain a unique, encrypted copy of the AES master key needed to retrieve secret data.
User keys may be created by users individually, however they are of no use until they have been activated by a user who already has access to retrieve secret data.
User keys may be created by users individually, however they are of no use until they have been activated by a user who already possesses an active user key.
## Creating the First User Key
When NetBox is first installed, it contains no encryption keys. Before it can store secrets, a user (typically the super user) must create a user key. This can be done by navigating to Profile > User Key.
When NetBox is first installed, it contains no encryption keys. Before it can store secrets, a user (typically the superuser) must create a user key. This can be done by navigating to Profile > User Key.
To create a user key, you can either generate a new RSA key pair, or upload the public key belonging to a pair you already have. If generating a new key pair, **you must save the private key** locally before saving your new user key. Once your user key has been created, its public key will be displayed under your profile.

View File

@@ -0,0 +1,20 @@
NetBox supports the assignment of resources to tenant organizations. Typically, these are used to represent individual customers of or internal departments within the organization using NetBox.
# Tenants
A tenant represents a discrete organization. The following objects can be assigned to tenants:
* Sites
* Racks
* Devices
* VRFs
* Prefixes
* IP addresses
* VLANs
* Circuits
If a prefix or IP address is not assigned to a tenant, it will appear to inherit the tenant to which its parent VRF is assigned, if any.
### Tenant Groups
Tenants can be grouped by type. For instance, you might create one group called "Customers" and one called "Acquisitions." The assignment of tenants to groups is optional.

View File

@@ -0,0 +1,29 @@
NetBox supports the definition of virtual machines arranged in clusters. A cluster can optionally have physical host devices associated with it.
# Clusters
A cluster is a logical grouping of physical resources within which virtual machines run. A cluster must be assigned a type, and may optionally be assigned an organizational group.
Physical devices (from NetBox's DCIM component) may be associated with clusters as hosts. This allows users to track on which host(s) a particular VM may reside. However, NetBox does not support pinning a specific VM within a cluster to a particular host device.
### Cluster Types
A cluster type represents a technology or mechanism by which a cluster is formed. For example, you might create a cluster type named "VMware vSphere" for a locally hosted cluster or "DigitalOcean NYC3" for one hosted by a cloud provider.
### Cluster Groups
Cluster groups may be created for the purpose of organizing clusters.
---
# Virtual Machines
A virtual machine represents a virtual compute instance hosted within a cluster. Each VM must be associated with exactly one cluster.
Like devices, each VM can have interfaces created on it. These behave similarly to device interfaces, and can be assigned IP addresses, however given their virtual nature they cannot be connected to other interfaces. VMs can also be assigned layer four services. Unlike physical devices, VMs cannot be assigned console or power ports, or device bays.
The following resources can be defined for each VM:
* vCPU count
* Memory (MB)
* Disk space (GB)

View File

@@ -0,0 +1,53 @@
# Utility Views
Utility views are reusable views that handle common CRUD tasks, such as listing and updating objects. Some views operate on individual objects, whereas others (referred to as "bulk" views) operate on multiple objects at once.
## Individual Views
### ObjectListView
Generates a paginated table of objects from a given queryset, which may optionally be filtered.
### ObjectEditView
Updates an object identified by a primary key (PK) or slug. If no existing object is specified, a new object will be created.
### ObjectDeleteView
Deletes an object. The user is redirected to a confirmation page before the deletion is executed.
## Bulk Views
### BulkCreateView
Creates multiple objects at once based on a given pattern. Currently used only for IP addresses.
### BulkImportView
Accepts CSV-formatted data and creates a new object for each line. Creation is all-or-none.
### BulkEditView
Applies changes to multiple objects at once in a two-step operation. First, the list of PKs for selected objects is POSTed and an edit form is presented to the user. On submission of that form, the specified changes are made to all selected objects.
### BulkDeleteView
Deletes multiple objects. The user selects the objects to be deleted and confirms the deletion.
## Component Views
### ComponentCreateView
Create one or more component objects beloning to a parent object (e.g. interfaces attached to a device).
### ComponentEditView
A subclass of `ObjectEditView`: Updates an individual component object.
### ComponentDeleteView
A subclass of `ObjectDeleteView`: Deletes an individual component object.
### BulkComponentCreateView
Create a set of components objects for each of a selected set of parent objects. This view can be used e.g. to create multiple interfaces on multiple devices at once.

View File

@@ -6,6 +6,7 @@ NetBox is an open source web application designed to help manage and document co
* **Equipment racks** - Organized by group and site
* **Devices** - Types of devices and where they are installed
* **Connections** - Network, console, and power connections among devices
* **Virtualization** - Virtual machines and clusters
* **Data circuits** - Long-haul communications circuits and providers
* **Secrets** - Encrypted storage of sensitive credentials
@@ -46,7 +47,7 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and
| HTTP Service | nginx or Apache |
| WSGI Service | gunicorn or uWSGI |
| Application | Django/Python |
| Database | PostgreSQL |
| Database | PostgreSQL 9.4+ |
# Getting Started

View File

@@ -1,51 +0,0 @@
This guide demonstrates how to build and run NetBox as a Docker container. It assumes that the latest versions of [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) are already installed in your host.
# Quickstart
To get NetBox up and running:
```
git clone -b master https://github.com/digitalocean/netbox.git
cd netbox
docker-compose up -d
```
The application will be available on http://localhost/ after a few minutes.
Default credentials:
* Username: **admin**
* Password: **admin**
# Configuration
You can configure the app at runtime using variables (see `docker-compose.yml`). Possible environment variables include:
* SUPERUSER_NAME
* SUPERUSER_EMAIL
* SUPERUSER_PASSWORD
* ALLOWED_HOSTS
* DB_NAME
* DB_USER
* DB_PASSWORD
* DB_HOST
* DB_PORT
* SECRET_KEY
* EMAIL_SERVER
* EMAIL_PORT
* EMAIL_USERNAME
* EMAIL_PASSWORD
* EMAIL_TIMEOUT
* EMAIL_FROM
* LOGIN_REQUIRED
* MAINTENANCE_MODE
* NETBOX_USERNAME
* NETBOX_PASSWORD
* PAGINATE_COUNT
* TIME_ZONE
* DATE_FORMAT
* SHORT_DATE_FORMAT
* TIME_FORMAT
* SHORT_TIME_FORMAT
* DATETIME_FORMAT
* SHORT_DATETIME_FORMAT

View File

@@ -1,5 +1,4 @@
This guide explains how to implement LDAP authentication using an external server. User authentication will fall back to
built-in Django users in the event of a failure.
This guide explains how to implement LDAP authentication using an external server. User authentication will fall back to built-in Django users in the event of a failure.
# Requirements
@@ -7,28 +6,31 @@ built-in Django users in the event of a failure.
On Ubuntu:
```
```no-highlight
sudo apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev
```
On CentOS:
```
```no-highlight
sudo yum install -y python-devel openldap-devel
```
## Install django-auth-ldap
```
```no-highlight
sudo pip install django-auth-ldap
```
# Configuration
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`.
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`. Complete documentation of all `django-auth-ldap` configuration options is included in the project's [official documentation](http://django-auth-ldap.readthedocs.io/).
## 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
@@ -50,8 +52,13 @@ AUTH_LDAP_BIND_PASSWORD = "demo"
LDAP_IGNORE_CERT_ERRORS = True
```
STARTTLS can be configured by setting `AUTH_LDAP_START_TLS = True` and using the `ldap://` URI scheme.
## 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
@@ -67,17 +74,20 @@ AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
# You can map user attributes to Django attributes as so.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn"
"last_name": "sn",
"email": "mail"
}
```
# User Groups for Permissions
!!! info
When using Microsoft Active Directory, support for nested groups can be activated by using `NestedGroupOfNamesType()` instead of `GroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.
```python
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
# heirarchy.
# hierarchy.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("dc=example,dc=com", ldap.SCOPE_SUBTREE,
"(objectClass=group)")
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
@@ -99,3 +109,7 @@ AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
```
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
* `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.

View File

@@ -0,0 +1,33 @@
# Migration
Remove Python 2 packages
```no-highlight
# apt-get remove --purge -y python-dev python-pip
```
Install Python 3 packages
```no-highlight
# apt-get install -y python3 python3-dev python3-pip
```
Install Python Packages
```no-highlight
# cd /opt/netbox
# pip3 install -r requirements.txt
```
Gunicorn Update
```no-highlight
# pip uninstall gunicorn
# pip3 install gunicorn
```
Re-install LDAP Module (optional if using LDAP for auth)
```no-highlight
sudo pip3 install django-auth-ldap
```

View File

@@ -1,18 +1,42 @@
# Installation
NetBox requires following system dependencies:
This section of the documentation discusses installing and configuring the NetBox application.
* python2.7
* python-dev
* python-pip
* libxml2-dev
* libxslt1-dev
* libffi-dev
* graphviz
* libpq-dev
!!! note
Python 3 is strongly encouraged for new installations. Support for Python 2 will be discontinued in the near future. This documentation includes a guide on [migrating from Python 2 to Python 3](migrating-to-python3).
**Ubuntu**
Python 3:
```no-highlight
# apt-get install -y python3 python3-dev python3-setuptools build-essential libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev zlib1g-dev
# easy_install3 pip
```
# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev
Python 2:
```no-highlight
# apt-get install -y python2.7 python-dev python-setuptools build-essential libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev zlib1g-dev
# easy_install pip
```
**CentOS**
Python 3:
```no-highlight
# yum install -y epel-release
# yum install -y gcc python34 python34-devel python34-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel redhat-rpm-config
# easy_install-3.4 pip
```
Python 2:
```no-highlight
# yum install -y epel-release
# yum install -y gcc python2 python-devel python-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel redhat-rpm-config
# easy_install pip
```
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
@@ -21,7 +45,7 @@ You may opt to install NetBox either from a numbered release or by cloning the m
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive and extract it to your desired path. In this example, we'll use `/opt/netbox`.
```
```no-highlight
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
@@ -33,20 +57,27 @@ Download the [latest stable release](https://github.com/digitalocean/netbox/rele
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
```
# mkdir -p /opt/netbox/
# cd /opt/netbox/
```no-highlight
# mkdir -p /opt/netbox/ && cd /opt/netbox/
```
If `git` is not already installed, install it:
**Ubuntu**
```no-highlight
# apt-get install -y git
```
# sudo apt-get install -y git
**CentOS**
```no-highlight
# yum install -y git
```
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
```
```no-highlight
# git clone -b master https://github.com/digitalocean/netbox.git .
Cloning into '.'...
remote: Counting objects: 1994, done.
@@ -57,25 +88,49 @@ Resolving deltas: 100% (1495/1495), done.
Checking connectivity... done.
```
!!! warning
Ensure that the media directory (`/opt/netbox/netbox/media/` in this example) and all its subdirectories are writable by the user account as which NetBox runs. If the NetBox process does not have permission to write to this directory, attempts to upload files (e.g. image attachments) will fail. (The appropriate user account will vary by platform.)
`# chown -R netbox:netbox /opt/netbox/netbox/media/`
## Install Python Packages
Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
Python 3:
```no-highlight
# pip3 install -r requirements.txt
```
# sudo pip install -r requirements.txt
Python 2:
```no-highlight
# pip install -r requirements.txt
```
!!! note
If you encounter errors while installing the required packages, check that you're running a recent version of pip (v9.0.1 or higher) with the command `pip -V` or `pip3 -V`.
### NAPALM Automation
As of v2.1.0, NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to fetch live data from devices and return it to a requester via its REST API. Installation of NAPALM is optional. To enable it, install the `napalm` package using pip or pip3:
```no-highlight
# pip3 install napalm
```
# Configuration
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
```
```no-highlight
# cd netbox/netbox/
# cp configuration.example.py configuration.py
```
Open `configuration.py` with your preferred editor and set the following variables:
* ALLOWED_HOSTS
* DATABASE
* SECRET_KEY
@@ -86,7 +141,7 @@ This is a list of the valid hostnames by which this server can be reached. You m
Example:
```
```python
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
```
@@ -96,7 +151,7 @@ This parameter holds the database configuration details. You must define the use
Example:
```
```python
DATABASE = {
'NAME': 'netbox', # Database name
'USER': 'netbox', # PostgreSQL username
@@ -117,11 +172,14 @@ You may use the script located at `netbox/generate_secret_key.py` to generate a
# Run Database Migrations
Before NetBox can run, we need to install the database schema. This is done by running `./manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):
!!! warning
The examples on the rest of this page call the `python3` executable. Replace this with `python2` or `python` if you're using Python 2.
```
Before NetBox can run, we need to install the database schema. This is done by running `python3 manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):
```no-highlight
# cd /opt/netbox/netbox/
# ./manage.py migrate
# python3 manage.py migrate
Operations to perform:
Apply all migrations: dcim, sessions, admin, ipam, utilities, auth, circuits, contenttypes, extras, secrets, users
Running migrations:
@@ -138,19 +196,19 @@ If this step results in a PostgreSQL authentication error, ensure that the usern
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
```
# ./manage.py createsuperuser
```no-highlight
# python3 manage.py createsuperuser
Username: admin
Email address: admin@example.com
Password:
Password (again):
Password:
Password (again):
Superuser created successfully.
```
# Collect Static Files
```
# ./manage.py collectstatic
```no-highlight
# python3 manage.py collectstatic --no-input
You have requested to collect static files at the destination
location as specified in your settings:
@@ -163,12 +221,24 @@ Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: yes
```
# Load Initial Data (Optional)
NetBox ships with some initial data to help you get started: RIR definitions, common devices roles, etc. You can delete any seed data that you don't want to keep.
!!! note
This step is optional. It's perfectly fine to start using NetBox without using this initial data if you'd rather create everything from scratch.
```no-highlight
# python3 manage.py loaddata initial_data
Installed 43 object(s) from 4 fixture(s)
```
# Test the Application
At this point, NetBox should be able to run. We can verify this by starting a development instance:
```
# ./manage.py runserver 0.0.0.0:8000 --insecure
```no-highlight
# python3 manage.py runserver 0.0.0.0:8000 --insecure
Performing system checks...
System check identified no issues (0 silenced).
@@ -178,7 +248,7 @@ Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
```
Now if we navigate to the name or IP of the server (as defined in `ALLOWED_HOSTS`) we should be greeted with the NetBox home page. Note that this built-in web service is for development and testing purposes only. It is not suited for production use.
Now if we navigate to the name or IP of the server (as defined in `ALLOWED_HOSTS`) we should be greeted with the NetBox home page. Note that this built-in web service is for development and testing purposes only. **It is not suited for production use.**
!!! warning
If the test service does not run, or you cannot reach the NetBox home page, something has gone wrong. Do not proceed with the rest of this guide until the installation has been corrected.

View File

@@ -1,27 +1,56 @@
NetBox requires a PostgreSQL database to store data. MySQL is not supported, as NetBox leverage's PostgreSQL's built-in [network address types](https://www.postgresql.org/docs/9.1/static/datatype-net-types.html).
NetBox requires a PostgreSQL database to store data. This can be hosted locally or on a remote server. (Please note that MySQL is not supported, as NetBox leverages PostgreSQL's built-in [network address types](https://www.postgresql.org/docs/current/static/datatype-net-types.html).)
!!! note
The installation instructions provided here have been tested to work on Ubuntu 16.04 and CentOS 7.4. The particular commands needed to install dependencies on other distributions may vary significantly. Unfortunately, this is outside the control of the NetBox maintainers. Please consult your distribution's documentation for assistance with any errors.
!!! warning
NetBox v2.2 and later requires PostgreSQL 9.4 or higher.
# Installation
The following packages are needed to install PostgreSQL with Python support:
**Ubuntu**
* postgresql
* libpq-dev
* python-psycopg2
If a recent enough version of PostgreSQL is not available through your distribution's package manager, you'll need to install it from an official [PostgreSQL repository](https://wiki.postgresql.org/wiki/Apt).
```
# sudo apt-get install -y postgresql libpq-dev python-psycopg2
```no-highlight
# apt-get update
# apt-get install -y postgresql libpq-dev
```
# Configuration
**CentOS**
CentOS 7.4 does not ship with a recent enough version of PostgreSQL, so it will need to be installed from an external repository. The instructions below show the installation of PostgreSQL 9.6.
```no-highlight
# yum install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
# yum install postgresql96 postgresql96-server postgresql96-devel
# /usr/pgsql-9.6/bin/postgresql96-setup initdb
```
CentOS users should modify the PostgreSQL configuration to accept password-based authentication by replacing `ident` with `md5` for all host entries within `/var/lib/pgsql/9.6/data/pg_hba.conf`. For example:
```no-highlight
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
```
Then, start the service and enable it to run at boot:
```no-highlight
# systemctl start postgresql-9.6
# systemctl enable postgresql-9.6
```
# Database Creation
At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. This is done with the following commands.
!!! danger
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
```
```no-highlight
# sudo -u postgres psql
psql (9.3.13)
psql (9.4.5)
Type "help" for help.
postgres=# CREATE DATABASE netbox;
@@ -33,10 +62,10 @@ GRANT
postgres=# \q
```
You can verify that authentication works issuing the following command and providing the configured password:
You can verify that authentication works issuing the following command and providing the configured password. (Replace `localhost` with your database server if using a remote database.)
```
# psql -U netbox -h localhost -W
```no-highlight
# psql -U netbox -W -h localhost netbox
```
If successful, you will enter a `postgres` prompt. Type `\q` to exit.
If successful, you will enter a `netbox` prompt. Type `\q` to exit.

View File

@@ -8,7 +8,7 @@ Download the [latest stable release](https://github.com/digitalocean/netbox/rele
Download and extract the latest version:
```
```no-highlight
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
@@ -17,21 +17,33 @@ Download and extract the latest version:
Copy the 'configuration.py' you created when first installing to the new version:
```no-highlight
# cp /opt/netbox-X.Y.Z/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/configuration.py
```
# cp /opt/netbox-X.Y.Z/configuration.py /opt/netbox/configuration.py
Be sure to replicate your uploaded media as well. (The exact action necessary will depend on where you choose to store your media, but in general moving or copying the media directory will suffice.)
```no-highlight
# cp -pr /opt/netbox-X.Y.Z/netbox/media/ /opt/netbox/netbox/
```
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
```
```no-highlight
# cp /opt/netbox-X.Y.Z/gunicorn_config.py /opt/netbox/gunicorn_config.py
```
Copy the LDAP configuration if using LDAP:
```no-highlight
# cp /opt/netbox-X.Y.Z/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/ldap_config.py
```
## Option B: Clone the Git Repository (latest master release)
This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most recent iteration of the master branch:
```
```no-highlight
# cd /opt/netbox
# git checkout master
# git pull origin master
@@ -42,20 +54,35 @@ This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).
```
```no-highlight
# ./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
* Applies any database migrations that were included in the release
* Collects all static files to be served by the HTTP service
!!! note
It's possible that the upgrade script will display a notice warning of unreflected database migrations:
Your models have changes that are not yet reflected in a migration, and so won't be applied.
Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.
This may occur due to semantic differences in environment, and can be safely ignored. Never attempt to create new migrations unless you are intentionally modifying the database schema.
# Restart the WSGI Service
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
```
```no-highlight
# sudo supervisorctl restart netbox
```

View File

@@ -1,28 +1,27 @@
We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for the purposes of this guide. For web servers, we provide example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/2.4). (You are of course free to use whichever combination of HTTP and WSGI services you'd like.) We'll also use [supervisord](http://supervisord.org/) to enable service persistence.
!!! info
For the sake of brevity, only Ubuntu 16.04 instructions are provided here, but this sort of web server and WSGI configuration is not unique to NetBox. Please consult your distribution's documentation for assistance if needed.
# Web Server Installation
We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for the purposes of this guide. For web servers, we provide example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/2.4). (You are of course free to use whichever combination of HTTP and WSGI services you'd like.) We'll also use [supervisord](http://supervisord.org/) to enable service persistence.
```
# sudo apt-get install -y gunicorn supervisor
```
## Option A: nginx
The following will serve as a minimal nginx configuration. Be sure to modify your server name and installation path appropriately.
```
# sudo apt-get install -y nginx
```no-highlight
# apt-get install -y nginx
```
Once nginx is installed, proceed with the following configuration:
Once nginx is installed, save the following configuration to `/etc/nginx/sites-available/netbox`. Be sure to replace `netbox.example.com` with the domain name or IP address of your installation. (This should match the value configured for `ALLOWED_HOSTS` in `configuration.py`.)
```
```nginx
server {
listen 80;
server_name netbox.example.com;
access_log off;
client_max_body_size 25m;
location /static/ {
alias /opt/netbox/netbox/static/;
@@ -38,32 +37,31 @@ server {
}
```
Save this configuration to `/etc/nginx/sites-available/netbox`. Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sites-enabled` directory to the configuration file you just created.
Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sites-enabled` directory to the configuration file you just created.
```
```no-highlight
# cd /etc/nginx/sites-enabled/
# rm default
# ln -s /etc/nginx/sites-available/netbox
# ln -s /etc/nginx/sites-available/netbox
```
Restart the nginx service to use the new configuration.
```
```no-highlight
# service nginx restart
* Restarting nginx nginx
```
To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04).
To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04).
## Option B: Apache
```
# sudo apt-get install -y apache2
```no-highlight
# apt-get install -y apache2
```
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
```
```apache
<VirtualHost *:80>
ProxyPreserveHost On
@@ -71,6 +69,9 @@ Once Apache is installed, proceed with the following configuration (Be sure to m
Alias /static /opt/netbox/netbox/static
# Needed to allow token-based API authentication
WSGIPassAuthorization on
<Directory /opt/netbox/netbox/static>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
@@ -81,6 +82,7 @@ Once Apache is installed, proceed with the following configuration (Be sure to m
ProxyPass !
</Location>
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
ProxyPass / http://127.0.0.1:8001/
ProxyPassReverse / http://127.0.0.1:8001/
</VirtualHost>
@@ -88,20 +90,27 @@ Once Apache is installed, proceed with the following configuration (Be sure to m
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
```
```no-highlight
# a2enmod proxy
# a2enmod proxy_http
# a2enmod headers
# a2ensite netbox
# service apache2 restart
```
To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-14-04).
To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04).
# gunicorn Installation
Save the following configuration file in the root netbox installation path (in this example, `/opt/netbox/`) as `gunicorn_config.py`. Be sure to verify the location of the gunicorn executable (e.g. `which gunicorn`) and to update the `pythonpath` variable if needed.
Install gunicorn using `pip3` (Python 3) or `pip` (Python 2):
```no-highlight
# pip3 install gunicorn
```
Save the following configuration in the root netbox installation path as `gunicorn_config.py` (e.g. `/opt/netbox/gunicorn_config.py` per our example installation). Be sure to verify the location of the gunicorn executable on your server (e.g. `which gunicorn`) and to update the `pythonpath` variable if needed. If using CentOS/RHEL, change the username from `www-data` to `nginx` or `apache`.
```no-highlight
command = '/usr/bin/gunicorn'
pythonpath = '/opt/netbox/netbox'
bind = '127.0.0.1:8001'
@@ -111,18 +120,24 @@ user = 'www-data'
# supervisord Installation
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
Install supervisor:
```no-highlight
# apt-get install -y supervisor
```
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed. If using CentOS/RHEL, change the username from `www-data` to `nginx` or `apache`.
```no-highlight
[program:netbox]
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
directory = /opt/netbox/netbox/
user = www-data
```
Finally, restart the supervisor service to detect and run the gunicorn service:
Then, restart the supervisor service to detect and run the gunicorn service:
```
```no-highlight
# service supervisor restart
```

View File

@@ -0,0 +1,131 @@
# NetBox Reports
A NetBox report is a mechanism for validating the integrity of data within NetBox. Running a report allows the user to verify that the objects defined within NetBox meet certain arbitrary conditions. For example, you can write reports to check that:
* All top-of-rack switches have a console connection
* Every router has a loopback interface with an IP address assigned
* Each interface description conforms to a standard format
* Every site has a minimum set of VLANs defined
* All IP addresses have a parent prefix
...and so on. Reports are completely customizable, so there's practically no limit to what you can test for.
## Writing Reports
Reports must be saved as files in the [`REPORTS_ROOT`](../configuration/optional-settings/#reports_root) path (which defaults to `netbox/reports/`). Each file created within this path is considered a separate module. Each module holds one or more reports (Python classes), each of which performs a certain function. The logic of each report is broken into discrete test methods, each of which applies a small portion of the logic comprising the overall test.
!!! warning
The reports path includes a file named `__init__.py`, which registers the path as a Python module. Do not delete this file.
For example, we can create a module named `devices.py` to hold all of our reports which pertain to devices in NetBox. Within that module, we might define several reports. Each report is defined as a Python class inheriting from `extras.reports.Report`.
```
from extras.reports import Report
class DeviceConnectionsReport(Report):
description = "Validate the minimum physical connections for each device"
class DeviceIPsReport(Report):
description = "Check that every device has a primary IP address assigned"
```
Within each report class, we'll create a number of test methods to execute our report's logic. In DeviceConnectionsReport, for instance, we want to ensure that every live device has a console connection, an out-of-band management connection, and two power connections.
```
from dcim.constants import CONNECTION_STATUS_PLANNED, STATUS_ACTIVE
from dcim.models import ConsolePort, Device, PowerPort
from extras.reports import Report
class DeviceConnectionsReport(Report):
description = "Validate the minimum physical connections for each device"
def test_console_connection(self):
# Check that every console port for every active device has a connection defined.
for console_port in ConsolePort.objects.select_related('device').filter(device__status=STATUS_ACTIVE):
if console_port.cs_port is None:
self.log_failure(
console_port.device,
"No console connection defined for {}".format(console_port.name)
)
elif console_port.connection_status == CONNECTION_STATUS_PLANNED:
self.log_warning(
console_port.device,
"Console connection for {} marked as planned".format(console_port.name)
)
else:
self.log_success(console_port.device)
def test_power_connections(self):
# Check that every active device has at least two connected power supplies.
for device in Device.objects.filter(status=STATUS_ACTIVE):
connected_ports = 0
for power_port in PowerPort.objects.filter(device=device):
if power_port.power_outlet is not None:
connected_ports += 1
if power_port.connection_status == CONNECTION_STATUS_PLANNED:
self.log_warning(
device,
"Power connection for {} marked as planned".format(power_port.name)
)
if connected_ports < 2:
self.log_failure(
device,
"{} connected power supplies found (2 needed)".format(connected_ports)
)
else:
self.log_success(device)
```
As you can see, reports are completely customizable. Validation logic can be as simple or as complex as needed.
!!! warning
Reports should never alter data: If you find yourself using the `create()`, `save()`, `update()`, or `delete()` methods on objects within reports, stop and re-evaluate what you're trying to accomplish. Note that there are no safeguards against the accidental alteration or destruction of data.
The following methods are available to log results within a report:
* log(message)
* log_success(object, message=None)
* log_info(object, message)
* log_warning(object, message)
* log_failure(object, message)
The recording of one or more failure messages will automatically flag a report as failed. It is advised to log a success for each object that is evaluated so that the results will reflect how many objects are being reported on. (The inclusion of a log message is optional for successes.) Messages recorded with `log()` will appear in a report's results but are not associated with a particular object or status.
To perform additional tasks, such as sending an email or calling a webhook, after a report has been run, extend the `post_run()` method. The status of the report is available as `self.failed` and the results object is `self.result`.
Once you have created a report, it will appear in the reports list. Initially, reports will have no results associated with them. To generate results, run the report.
## Running Reports
### Via the Web UI
Reports can be run via the web UI by navigating to the report and clicking the "run report" button at top right. Note that a user must have permission to create ReportResults in order to run reports. (Permissions can be assigned through the admin UI.)
Once a report has been run, its associated results will be included in the report view.
### Via the API
To run a report via the API, simply issue a POST request to its `run` endpoint. Reports are identified by their module and class name.
```
POST /api/extras/reports/<module>.<name>/run/
```
Our example report above would be called as:
```
POST /api/extras/reports/devices.DeviceConnectionsReport/run/
```
### Via the CLI
Reports can be run on the CLI by invoking the management command:
```
python3 manage.py runreport <module>
```
One or more report modules may be specified.

194
docs/miscellaneous/shell.md Normal file
View File

@@ -0,0 +1,194 @@
NetBox includes a Python shell within which objects can be directly queried, created, modified, and deleted. To enter the shell, run the following command:
```
./manage.py nbshell
```
This will launch a customized version of [the built-in Django shell](https://docs.djangoproject.com/en/dev/ref/django-admin/#shell) with all relevant NetBox models pre-loaded. (If desired, the stock Django shell is also available by executing `./manage.py shell`.)
```
$ ./manage.py nbshell
### NetBox interactive shell (jstretch-laptop)
### Python 2.7.6 | Django 1.11.3 | NetBox 2.1.0-dev
### lsmodels() will show available models. Use help(<model>) for more info.
```
The function `lsmodels()` will print a list of all available NetBox models:
```
>>> lsmodels()
DCIM:
ConsolePort
ConsolePortTemplate
ConsoleServerPort
ConsoleServerPortTemplate
Device
...
```
## Querying Objects
Objects are retrieved by forming a [Django queryset](https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-objects). The base queryset for an object takes the form `<model>.objects.all()`, which will return a (truncated) list of all objects of that type.
```
>>> Device.objects.all()
<QuerySet [<Device: TestDevice1>, <Device: TestDevice2>, <Device: TestDevice3>, <Device: TestDevice4>, <Device: TestDevice5>, '...(remaining elements truncated)...']>
```
Use a `for` loop to cycle through all objects in the list:
```
>>> for device in Device.objects.all():
... print(device.name, device.device_type)
...
(u'TestDevice1', <DeviceType: PacketThingy 9000>)
(u'TestDevice2', <DeviceType: PacketThingy 9000>)
(u'TestDevice3', <DeviceType: PacketThingy 9000>)
(u'TestDevice4', <DeviceType: PacketThingy 9000>)
(u'TestDevice5', <DeviceType: PacketThingy 9000>)
...
```
To count all objects matching the query, replace `all()` with `count()`:
```
>>> Device.objects.count()
1274
```
To retrieve a particular object (typically by its primary key or other unique field), use `get()`:
```
>>> Site.objects.get(pk=7)
<Site: Test Lab>
```
### Filtering Querysets
In most cases, you want to retrieve only a specific subset of objects. To filter a queryset, replace `all()` with `filter()` and pass one or more keyword arguments. For example:
```
>>> Device.objects.filter(status=STATUS_ACTIVE)
<QuerySet [<Device: TestDevice1>, <Device: TestDevice2>, <Device: TestDevice3>, <Device: TestDevice8>, <Device: TestDevice9>, '...(remaining elements truncated)...']>
```
Querysets support slicing to return a specific range of objects.
```
>>> Device.objects.filter(status=STATUS_ACTIVE)[:3]
<QuerySet [<Device: TestDevice1>, <Device: TestDevice2>, <Device: TestDevice3>]>
```
The `count()` method can be appended to the queryset to return a count of objects rather than the full list.
```
>>> Device.objects.filter(status=STATUS_ACTIVE).count()
982
```
Relationships with other models can be traversed by concatenating field names with a double-underscore. For example, the following will return all devices assigned to the tenant named "Pied Piper."
```
>>> Device.objects.filter(tenant__name='Pied Piper')
```
This approach can span multiple levels of relations. For example, the following will return all IP addresses assigned to a device in North America:
```
>>> IPAddress.objects.filter(interface__device__site__region__slug='north-america')
```
!!! note
While the above query is functional, it is very inefficient. There are ways to optimize such requests, however they are out of the scope of this document. For more information, see the [Django queryset method reference](https://docs.djangoproject.com/en/dev/ref/models/querysets/) documentation.
Reverse relationships can be traversed as well. For example, the following will find all devices with an interface named "em0":
```
>>> Device.objects.filter(interfaces__name='em0')
```
Character fields can be filtered against partial matches using the `contains` or `icontains` field lookup (the later of which is case-insensitive).
```
>>> Device.objects.filter(name__icontains='testdevice')
```
Similarly, numeric fields can be filtered by values less than, greater than, and/or equal to a given value.
```
>>> VLAN.objects.filter(vid__gt=2000)
```
Multiple filters can be combined to further refine a queryset.
```
>>> VLAN.objects.filter(vid__gt=2000, name__icontains='engineering')
```
To return the inverse of a filtered queryset, use `exclude()` instead of `filter()`.
```
>>> Device.objects.count()
4479
>>> Device.objects.filter(status=STATUS_ACTIVE).count()
4133
>>> Device.objects.exclude(status=STATUS_ACTIVE).count()
346
```
!!! info
The examples above are intended only to provide a cursory introduction to queryset filtering. For an exhaustive list of the available filters, please consult the [Django queryset API docs](https://docs.djangoproject.com/en/dev/ref/models/querysets/).
## Creating and Updating Objects
New objects can be created by instantiating the desired model, defining values for all required attributes, and calling `save()` on the instance.
```
>>> lab1 = Site.objects.get(pk=7)
>>> myvlan = VLAN(vid=123, name='MyNewVLAN', site=lab1)
>>> myvlan.save()
```
Alternatively, the above can be performed as a single operation:
```
>>> VLAN(vid=123, name='MyNewVLAN', site=Site.objects.get(pk=7)).save()
```
To modify an object, retrieve it, update the desired field(s), and call `save()` again.
```
>>> vlan = VLAN.objects.get(pk=1280)
>>> vlan.name
u'MyNewVLAN'
>>> vlan.name = 'BetterName'
>>> vlan.save()
>>> VLAN.objects.get(pk=1280).name
u'BetterName'
```
!!! warning
The Django ORM provides methods to create/edit many objects at once, namely `bulk_create()` and `update()`. These are best avoided in most cases as they bypass a model's built-in validation and can easily lead to database corruption if not used carefully.
## Deleting Objects
To delete an object, simply call `delete()` on its instance. This will return a dictionary of all objects (including related objects) which have been deleted as a result of this operation.
```
>>> vlan
<VLAN: 123 (BetterName)>
>>> vlan.delete()
(1, {u'extras.CustomFieldValue': 0, u'ipam.VLAN': 1})
```
To delete multiple objects at once, call `delete()` on a filtered queryset. It's a good idea to always sanity-check the count of selected objects _before_ deleting them.
```
>>> Device.objects.filter(name__icontains='test').count()
27
>>> Device.objects.filter(name__icontains='test').delete()
(35, {u'extras.CustomFieldValue': 0, u'dcim.DeviceBay': 0, u'secrets.Secret': 0, u'dcim.InterfaceConnection': 4, u'extras.ImageAttachment': 0, u'dcim.Device': 27, u'dcim.Interface': 4, u'dcim.ConsolePort': 0, u'dcim.PowerPort': 0})
```
!!! warning
Deletions are immediate and irreversible. Always think very carefully before calling `delete()` on an instance or queryset.

BIN
docs/netbox_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -8,7 +8,7 @@ pages:
- 'Web Server': 'installation/web-server.md'
- 'LDAP (Optional)': 'installation/ldap.md'
- 'Upgrading': 'installation/upgrading.md'
- 'Alternate Install: Docker': 'installation/docker.md'
- 'Migrating to Python3': 'installation/migrating-to-python3.md'
- 'Configuration':
- 'Mandatory Settings': 'configuration/mandatory-settings.md'
- 'Optional Settings': 'configuration/optional-settings.md'
@@ -17,8 +17,19 @@ pages:
- 'DCIM': 'data-model/dcim.md'
- 'IPAM': 'data-model/ipam.md'
- 'Secrets': 'data-model/secrets.md'
- 'Tenancy': 'data-model/tenancy.md'
- 'Virtualization': 'data-model/virtualization.md'
- 'Extras': 'data-model/extras.md'
- 'API Integration': 'api-integration.md'
- 'API':
- 'Overview': 'api/overview.md'
- 'Authentication': 'api/authentication.md'
- 'Working with Secrets': 'api/working-with-secrets.md'
- 'Examples': 'api/examples.md'
- 'Miscellaneous':
- 'Reports': 'miscellaneous/reports.md'
- 'Shell': 'miscellaneous/shell.md'
- 'Development':
- 'Utility Views': 'development/utility-views.md'
markdown_extensions:
- admonition:

View File

@@ -0,0 +1 @@
default_app_config = 'circuits.apps.CircuitsConfig'

View File

@@ -1,30 +0,0 @@
from django.contrib import admin
from .models import Provider, CircuitType, Circuit
@admin.register(Provider)
class ProviderAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['name'],
}
list_display = ['name', 'slug', 'asn']
@admin.register(CircuitType)
class CircuitTypeAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['name'],
}
list_display = ['name', 'slug']
@admin.register(Circuit)
class CircuitAdmin(admin.ModelAdmin):
list_display = ['cid', 'provider', 'type', 'site', 'install_date', 'port_speed', 'commit_rate', 'xconnect_id']
list_filter = ['provider']
exclude = ['interface']
def get_queryset(self, request):
qs = super(CircuitAdmin, self).get_queryset(request)
return qs.select_related('provider', 'type', 'site')

View File

@@ -1,60 +1,122 @@
from __future__ import unicode_literals
from rest_framework import serializers
from circuits.models import Provider, CircuitType, Circuit
from dcim.api.serializers import SiteNestedSerializer, InterfaceNestedSerializer
from circuits.constants import CIRCUIT_STATUS_CHOICES
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
from extras.api.customfields import CustomFieldModelSerializer
from tenancy.api.serializers import NestedTenantSerializer
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
#
# Providers
#
class ProviderSerializer(serializers.ModelSerializer):
class ProviderSerializer(CustomFieldModelSerializer):
class Meta:
model = Provider
fields = ['id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
fields = [
'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
'custom_fields', 'created', 'last_updated',
]
class ProviderNestedSerializer(ProviderSerializer):
class NestedProviderSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
class Meta(ProviderSerializer.Meta):
fields = ['id', 'name', 'slug']
class Meta:
model = Provider
fields = ['id', 'url', 'name', 'slug']
class WritableProviderSerializer(CustomFieldModelSerializer):
class Meta:
model = Provider
fields = [
'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
'custom_fields', 'created', 'last_updated',
]
#
# Circuit types
#
class CircuitTypeSerializer(serializers.ModelSerializer):
class CircuitTypeSerializer(ValidatedModelSerializer):
class Meta:
model = CircuitType
fields = ['id', 'name', 'slug']
class CircuitTypeNestedSerializer(CircuitTypeSerializer):
class NestedCircuitTypeSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
class Meta(CircuitTypeSerializer.Meta):
pass
class Meta:
model = CircuitType
fields = ['id', 'url', 'name', 'slug']
#
# Circuits
#
class CircuitSerializer(serializers.ModelSerializer):
provider = ProviderNestedSerializer()
type = CircuitTypeNestedSerializer()
site = SiteNestedSerializer()
interface = InterfaceNestedSerializer()
class CircuitSerializer(CustomFieldModelSerializer):
provider = NestedProviderSerializer()
status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES)
type = NestedCircuitTypeSerializer()
tenant = NestedTenantSerializer()
class Meta:
model = Circuit
fields = ['id', 'cid', 'provider', 'type', 'site', 'interface', 'install_date', 'port_speed', 'commit_rate',
'xconnect_id', 'comments']
fields = [
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
'comments', 'custom_fields', 'created', 'last_updated',
]
class CircuitNestedSerializer(CircuitSerializer):
class NestedCircuitSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
class Meta(CircuitSerializer.Meta):
fields = ['id', 'cid']
class Meta:
model = Circuit
fields = ['id', 'url', 'cid']
class WritableCircuitSerializer(CustomFieldModelSerializer):
class Meta:
model = Circuit
fields = [
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
'comments', 'custom_fields', 'created', 'last_updated',
]
#
# Circuit Terminations
#
class CircuitTerminationSerializer(serializers.ModelSerializer):
circuit = NestedCircuitSerializer()
site = NestedSiteSerializer()
interface = InterfaceSerializer()
class Meta:
model = CircuitTermination
fields = [
'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
]
class WritableCircuitTerminationSerializer(ValidatedModelSerializer):
class Meta:
model = CircuitTermination
fields = [
'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
]

View File

@@ -1,25 +1,31 @@
from django.conf.urls import url
from __future__ import unicode_literals
from extras.models import GRAPH_TYPE_PROVIDER
from extras.api.views import GraphListView
from rest_framework import routers
from .views import *
from . import views
urlpatterns = [
class CircuitsRootView(routers.APIRootView):
"""
Circuits API root view
"""
def get_view_name(self):
return 'Circuits'
# Providers
url(r'^providers/$', ProviderListView.as_view(), name='provider_list'),
url(r'^providers/(?P<pk>\d+)/$', ProviderDetailView.as_view(), name='provider_detail'),
url(r'^providers/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_PROVIDER},
name='provider_graphs'),
# Circuit types
url(r'^circuit-types/$', CircuitTypeListView.as_view(), name='circuittype_list'),
url(r'^circuit-types/(?P<pk>\d+)/$', CircuitTypeDetailView.as_view(), name='circuittype_detail'),
router = routers.DefaultRouter()
router.APIRootView = CircuitsRootView
# Circuits
url(r'^circuits/$', CircuitListView.as_view(), name='circuit_list'),
url(r'^circuits/(?P<pk>\d+)/$', CircuitDetailView.as_view(), name='circuit_detail'),
# Field choices
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, base_name='field-choice')
]
# Providers
router.register(r'providers', views.ProviderViewSet)
# Circuits
router.register(r'circuit-types', views.CircuitTypeViewSet)
router.register(r'circuits', views.CircuitViewSet)
router.register(r'circuit-terminations', views.CircuitTerminationViewSet)
app_name = 'circuits-api'
urlpatterns = router.urls

View File

@@ -1,55 +1,76 @@
from rest_framework import generics
from __future__ import unicode_literals
from circuits.models import Provider, CircuitType, Circuit
from circuits.filters import CircuitFilter
from django.shortcuts import get_object_or_404
from rest_framework.decorators import detail_route
from rest_framework.response import Response
from circuits import filters
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_PROVIDER
from utilities.api import FieldChoicesViewSet, ModelViewSet
from . import serializers
class ProviderListView(generics.ListAPIView):
"""
List all providers
"""
#
# Field choices
#
class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(CircuitTermination, ['term_side']),
)
#
# Providers
#
class ProviderViewSet(CustomFieldModelViewSet):
queryset = Provider.objects.all()
serializer_class = serializers.ProviderSerializer
write_serializer_class = serializers.WritableProviderSerializer
filter_class = filters.ProviderFilter
@detail_route()
def graphs(self, request, pk=None):
"""
A convenience method for rendering graphs for a particular provider.
"""
provider = get_object_or_404(Provider, pk=pk)
queryset = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER)
serializer = RenderedGraphSerializer(queryset, many=True, context={'graphed_object': provider})
return Response(serializer.data)
class ProviderDetailView(generics.RetrieveAPIView):
"""
Retrieve a single provider
"""
queryset = Provider.objects.all()
serializer_class = serializers.ProviderSerializer
#
# Circuit Types
#
class CircuitTypeListView(generics.ListAPIView):
"""
List all circuit types
"""
class CircuitTypeViewSet(ModelViewSet):
queryset = CircuitType.objects.all()
serializer_class = serializers.CircuitTypeSerializer
filter_class = filters.CircuitTypeFilter
class CircuitTypeDetailView(generics.RetrieveAPIView):
"""
Retrieve a single circuit type
"""
queryset = CircuitType.objects.all()
serializer_class = serializers.CircuitTypeSerializer
#
# Circuits
#
class CircuitListView(generics.ListAPIView):
"""
List circuits (filterable)
"""
queryset = Circuit.objects.select_related('type', 'provider', 'site', 'interface__device')
class CircuitViewSet(CustomFieldModelViewSet):
queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
serializer_class = serializers.CircuitSerializer
filter_class = CircuitFilter
write_serializer_class = serializers.WritableCircuitSerializer
filter_class = filters.CircuitFilter
class CircuitDetailView(generics.RetrieveAPIView):
"""
Retrieve a single circuit
"""
queryset = Circuit.objects.select_related('type', 'provider', 'site', 'interface__device')
serializer_class = serializers.CircuitSerializer
#
# Circuit Terminations
#
class CircuitTerminationViewSet(ModelViewSet):
queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
serializer_class = serializers.CircuitTerminationSerializer
write_serializer_class = serializers.WritableCircuitTerminationSerializer
filter_class = filters.CircuitTerminationFilter

11
netbox/circuits/apps.py Normal file
View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class CircuitsConfig(AppConfig):
name = "circuits"
verbose_name = "Circuits"
def ready(self):
import circuits.signals

View File

@@ -0,0 +1,26 @@
from __future__ import unicode_literals
# Circuit statuses
CIRCUIT_STATUS_DEPROVISIONING = 0
CIRCUIT_STATUS_ACTIVE = 1
CIRCUIT_STATUS_PLANNED = 2
CIRCUIT_STATUS_PROVISIONING = 3
CIRCUIT_STATUS_OFFLINE = 4
CIRCUIT_STATUS_DECOMMISSIONED = 5
CIRCUIT_STATUS_CHOICES = [
[CIRCUIT_STATUS_PLANNED, 'Planned'],
[CIRCUIT_STATUS_PROVISIONING, 'Provisioning'],
[CIRCUIT_STATUS_ACTIVE, 'Active'],
[CIRCUIT_STATUS_OFFLINE, 'Offline'],
[CIRCUIT_STATUS_DEPROVISIONING, 'Deprovisioning'],
[CIRCUIT_STATUS_DECOMMISSIONED, 'Decommissioned'],
]
# CircuitTermination sides
TERM_SIDE_A = 'A'
TERM_SIDE_Z = 'Z'
TERM_SIDE_CHOICES = (
(TERM_SIDE_A, 'A'),
(TERM_SIDE_Z, 'Z'),
)

View File

@@ -1,23 +1,29 @@
import django_filters
from __future__ import unicode_literals
import django_filters
from django.db.models import Q
from dcim.models import Site
from .models import Provider, Circuit, CircuitType
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NumericInFilter
from .constants import CIRCUIT_STATUS_CHOICES
from .models import Provider, Circuit, CircuitTermination, CircuitType
class ProviderFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',
class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='circuits__site',
name='circuits__terminations__site',
queryset=Site.objects.all(),
label='Site',
)
site = django_filters.ModelMultipleChoiceFilter(
name='circuits__site',
name='circuits__terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -25,50 +31,74 @@ class ProviderFilter(django_filters.FilterSet):
class Meta:
model = Provider
fields = ['q', 'name', 'account', 'asn']
fields = ['name', 'slug', 'asn', 'account']
def search(self, queryset, value):
value = value.strip()
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(account__icontains=value)
Q(account__icontains=value) |
Q(noc_contact__icontains=value) |
Q(admin_contact__icontains=value) |
Q(comments__icontains=value)
)
class CircuitFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',
class CircuitTypeFilter(django_filters.FilterSet):
class Meta:
model = CircuitType
fields = ['name', 'slug']
class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
provider_id = django_filters.ModelMultipleChoiceFilter(
name='provider',
queryset=Provider.objects.all(),
label='Provider (ID)',
)
provider = django_filters.ModelMultipleChoiceFilter(
name='provider',
name='provider__slug',
queryset=Provider.objects.all(),
to_field_name='slug',
label='Provider (slug)',
)
type_id = django_filters.ModelMultipleChoiceFilter(
name='type',
queryset=CircuitType.objects.all(),
label='Circuit type (ID)',
)
type = django_filters.ModelMultipleChoiceFilter(
name='type',
name='type__slug',
queryset=CircuitType.objects.all(),
to_field_name='slug',
label='Circuit type (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=CIRCUIT_STATUS_CHOICES,
null_value=None
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='site',
name='terminations__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site',
name='terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -76,8 +106,49 @@ class CircuitFilter(django_filters.FilterSet):
class Meta:
model = Circuit
fields = ['q', 'provider_id', 'provider', 'type_id', 'type', 'site_id', 'site', 'interface', 'install_date']
fields = ['cid', 'install_date', 'commit_rate']
def search(self, queryset, value):
value = value.strip()
return queryset.filter(cid__icontains=value)
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(cid__icontains=value) |
Q(terminations__xconnect_id__icontains=value) |
Q(terminations__pp_info__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
).distinct()
class CircuitTerminationFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
circuit_id = django_filters.ModelMultipleChoiceFilter(
queryset=Circuit.objects.all(),
label='Circuit',
)
site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
class Meta:
model = CircuitTermination
fields = ['term_side', 'port_speed', 'upstream_speed', 'xconnect_id']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(circuit__cid__icontains=value) |
Q(xconnect_id__icontains=value) |
Q(pp_info__icontains=value)
).distinct()

View File

@@ -0,0 +1,26 @@
[
{
"model": "circuits.circuittype",
"pk": 1,
"fields": {
"name": "Internet",
"slug": "internet"
}
},
{
"model": "circuits.circuittype",
"pk": 2,
"fields": {
"name": "Private WAN",
"slug": "private-wan"
}
},
{
"model": "circuits.circuittype",
"pk": 3,
"fields": {
"name": "Out-of-Band",
"slug": "out-of-band"
}
}
]

View File

@@ -1,19 +1,25 @@
from __future__ import unicode_literals
from django import forms
from django.db.models import Count
from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
from dcim.models import Site, Device, Interface, Rack
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from tenancy.forms import TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, Livesearch, SmallTextarea, SlugField,
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin,
ChainedModelChoiceField, CommentField, CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField,
)
from .models import Circuit, CircuitType, Provider
from .constants import CIRCUIT_STATUS_CHOICES
from .models import Circuit, CircuitTermination, CircuitType, Provider
#
# Providers
#
class ProviderForm(forms.ModelForm, BootstrapMixin):
class ProviderForm(BootstrapMixin, CustomFieldForm):
slug = SlugField()
comments = CommentField()
@@ -33,42 +39,45 @@ class ProviderForm(forms.ModelForm, BootstrapMixin):
}
class ProviderFromCSVForm(forms.ModelForm):
class ProviderCSVForm(forms.ModelForm):
slug = SlugField()
class Meta:
model = Provider
fields = ['name', 'slug', 'asn', 'account', 'portal_url']
fields = Provider.csv_headers
help_texts = {
'name': 'Provider name',
'asn': '32-bit autonomous system number',
'portal_url': 'Portal URL',
'comments': 'Free-form comments',
}
class ProviderImportForm(BulkImportForm, BootstrapMixin):
csv = CSVDataField(csv_form=ProviderFromCSVForm)
class ProviderBulkEditForm(forms.Form, BootstrapMixin):
class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
asn = forms.IntegerField(required=False, label='ASN')
account = forms.CharField(max_length=30, required=False, label='Account number')
portal_url = forms.URLField(required=False, label='Portal')
noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
comments = CommentField()
comments = CommentField(widget=SmallTextarea)
class Meta:
nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
def provider_site_choices():
site_choices = Site.objects.all()
return [(s.slug, s.name) for s in site_choices]
class ProviderFilterForm(forms.Form, BootstrapMixin):
site = forms.MultipleChoiceField(required=False, choices=provider_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Provider
q = forms.CharField(required=False, label='Search')
site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')
asn = forms.IntegerField(required=False, label='ASN')
#
# Circuit types
#
class CircuitTypeForm(forms.ModelForm, BootstrapMixin):
class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
slug = SlugField()
class Meta:
@@ -76,130 +85,204 @@ class CircuitTypeForm(forms.ModelForm, BootstrapMixin):
fields = ['name', 'slug']
class CircuitTypeCSVForm(forms.ModelForm):
slug = SlugField()
class Meta:
model = CircuitType
fields = CircuitType.csv_headers
help_texts = {
'name': 'Name of circuit type',
}
#
# Circuits
#
class CircuitForm(forms.ModelForm, BootstrapMixin):
site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.Select(attrs={'filter-for': 'rack'}))
rack = forms.ModelChoiceField(queryset=Rack.objects.all(), required=False, label='Rack',
widget=APISelect(api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'device'}))
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
attrs={'filter-for': 'interface'}))
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
query_key='q', query_url='dcim-api:device_list', field_to_update='device')
)
interface = forms.ModelChoiceField(queryset=Interface.objects.all(), required=False, label='Interface',
widget=APISelect(api_url='/api/dcim/devices/{{device}}/interfaces/?type=physical',
disabled_indicator='is_connected'))
class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
comments = CommentField()
class Meta:
model = Circuit
fields = [
'cid', 'type', 'provider', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date',
'port_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments'
'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
'comments',
]
help_texts = {
'cid': "Unique circuit ID",
'install_date': "Format: YYYY-MM-DD",
'commit_rate': "Committed rate",
}
class CircuitCSVForm(forms.ModelForm):
provider = forms.ModelChoiceField(
queryset=Provider.objects.all(),
to_field_name='name',
help_text='Name of parent provider',
error_messages={
'invalid_choice': 'Provider not found.'
}
)
type = forms.ModelChoiceField(
queryset=CircuitType.objects.all(),
to_field_name='name',
help_text='Type of circuit',
error_messages={
'invalid_choice': 'Invalid circuit type.'
}
)
status = CSVChoiceField(
choices=CIRCUIT_STATUS_CHOICES,
required=False,
help_text='Operational status'
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text='Name of assigned tenant',
error_messages={
'invalid_choice': 'Tenant not found.'
}
)
class Meta:
model = Circuit
fields = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
]
class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
status = forms.ChoiceField(choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), required=False, initial='')
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
description = forms.CharField(max_length=100, required=False)
comments = CommentField(widget=SmallTextarea)
class Meta:
nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Circuit
q = forms.CharField(required=False, label='Search')
type = FilterChoiceField(
queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug'
)
provider = FilterChoiceField(
queryset=Provider.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug'
)
status = AnnotatedMultipleChoiceField(
choices=CIRCUIT_STATUS_CHOICES,
annotate=Circuit.objects.all(),
annotate_field='status',
required=False
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug',
null_label='-- None --'
)
site = FilterChoiceField(
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
to_field_name='slug'
)
commit_rate = forms.IntegerField(required=False, min_value=0, label='Commit rate (Kbps)')
#
# Circuit terminations
#
class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
widget=forms.Select(
attrs={'filter-for': 'rack'}
)
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains=(
('site', 'site'),
),
required=False,
label='Rack',
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'device', 'nullable': 'true'}
)
)
device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains=(
('site', 'site'),
('rack', 'rack'),
),
required=False,
label='Device',
widget=APISelect(
api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
display_field='display_name',
attrs={'filter-for': 'interface'}
)
)
interface = ChainedModelChoiceField(
queryset=Interface.objects.connectable().select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b'
),
chains=(
('device', 'device'),
),
required=False,
label='Interface',
widget=APISelect(
api_url='/api/dcim/interfaces/?device_id={{device}}&type=physical',
disabled_indicator='is_connected'
)
)
class Meta:
model = CircuitTermination
fields = [
'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
'pp_info',
]
help_texts = {
'port_speed': "Physical circuit speed",
'commit_rate': "Commited rate",
'xconnect_id': "ID of the local cross-connect",
'pp_info': "Patch panel ID and port number(s)"
}
widgets = {
'term_side': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
super(CircuitForm, self).__init__(*args, **kwargs)
# Initialize helper selectors
instance = kwargs.get('instance')
if instance and instance.interface is not None:
initial = kwargs.get('initial', {}).copy()
initial['rack'] = instance.interface.device.rack
initial['device'] = instance.interface.device
kwargs['initial'] = initial
# If this circuit has been assigned to an interface, initialize rack and device
if self.instance.interface:
self.initial['rack'] = self.instance.interface.device.rack
self.initial['device'] = self.instance.interface.device
super(CircuitTerminationForm, self).__init__(*args, **kwargs)
# Limit rack choices
if self.is_bound:
self.fields['rack'].queryset = Rack.objects.filter(site__pk=self.data['site'])
elif self.initial.get('site'):
self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
else:
self.fields['rack'].choices = []
# Limit device choices
if self.is_bound and self.data.get('rack'):
self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack'])
elif self.initial.get('rack'):
self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
else:
self.fields['device'].choices = []
# Limit interface choices
if self.is_bound and self.data.get('device'):
interfaces = Interface.objects.filter(device=self.data['device'])\
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
self.fields['interface'].widget.attrs['initial'] = self.data.get('interface')
elif self.initial.get('device'):
interfaces = Interface.objects.filter(device=self.initial['device'])\
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
self.fields['interface'].widget.attrs['initial'] = self.initial.get('interface')
else:
interfaces = []
self.fields['interface'].choices = [
(iface.id, {
'label': iface.name,
'disabled': iface.is_connected and iface.id != self.fields['interface'].widget.attrs.get('initial'),
}) for iface in interfaces
]
class CircuitFromCSVForm(forms.ModelForm):
provider = forms.ModelChoiceField(Provider.objects.all(), to_field_name='name',
error_messages={'invalid_choice': 'Provider not found.'})
type = forms.ModelChoiceField(CircuitType.objects.all(), to_field_name='name',
error_messages={'invalid_choice': 'Invalid circuit type.'})
site = forms.ModelChoiceField(Site.objects.all(), to_field_name='name',
error_messages={'invalid_choice': 'Site not found.'})
class Meta:
model = Circuit
fields = ['cid', 'provider', 'type', 'site', 'install_date', 'port_speed', 'commit_rate', 'xconnect_id',
'pp_info']
class CircuitImportForm(BulkImportForm, BootstrapMixin):
csv = CSVDataField(csv_form=CircuitFromCSVForm)
class CircuitBulkEditForm(forms.Form, BootstrapMixin):
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
comments = CommentField()
def circuit_type_choices():
type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]
def circuit_provider_choices():
provider_choices = Provider.objects.annotate(circuit_count=Count('circuits'))
return [(p.slug, u'{} ({})'.format(p.name, p.circuit_count)) for p in provider_choices]
def circuit_site_choices():
site_choices = Site.objects.annotate(circuit_count=Count('circuits'))
return [(s.slug, u'{} ({})'.format(s.name, s.circuit_count)) for s in site_choices]
class CircuitFilterForm(forms.Form, BootstrapMixin):
type = forms.MultipleChoiceField(required=False, choices=circuit_type_choices)
provider = forms.MultipleChoiceField(required=False, choices=circuit_provider_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
site = forms.MultipleChoiceField(required=False, choices=circuit_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
# Mark connected interfaces as disabled
self.fields['interface'].choices = []
for iface in self.fields['interface'].queryset:
self.fields['interface'].choices.append(
(iface.id, {
'label': iface.name,
'disabled': iface.is_connected and iface.pk != self.initial.get('interface'),
})
)

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-07-26 21:59
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0001_initial'),
('circuits', '0003_provider_32bit_asn_support'),
]
operations = [
migrations.AddField(
model_name='circuit',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='tenancy.Tenant'),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-08 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0004_circuit_add_tenant'),
]
operations = [
migrations.AddField(
model_name='circuit',
name='upstream_speed',
field=models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', null=True, verbose_name=b'Upstream speed (Kbps)'),
),
]

View File

@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-12-13 16:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
def circuits_to_terms(apps, schema_editor):
Circuit = apps.get_model('circuits', 'Circuit')
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
for c in Circuit.objects.all():
CircuitTermination(
circuit=c,
term_side=b'A',
site=c.site,
interface=c.interface,
port_speed=c.port_speed,
upstream_speed=c.upstream_speed,
xconnect_id=c.xconnect_id,
pp_info=c.pp_info,
).save()
def terms_to_circuits(apps, schema_editor):
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
for ct in CircuitTermination.objects.filter(term_side='A'):
c = ct.circuit
c.site = ct.site
c.interface = ct.interface
c.port_speed = ct.port_speed
c.upstream_speed = ct.upstream_speed
c.xconnect_id = ct.xconnect_id
c.pp_info = ct.pp_info
c.save()
class Migration(migrations.Migration):
dependencies = [
('dcim', '0022_color_names_to_rgb'),
('circuits', '0005_circuit_add_upstream_speed'),
]
operations = [
migrations.CreateModel(
name='CircuitTermination',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('term_side', models.CharField(choices=[(b'A', b'A'), (b'Z', b'Z')], max_length=1,
verbose_name='Termination')),
('port_speed', models.PositiveIntegerField(verbose_name=b'Port speed (Kbps)')),
('upstream_speed',
models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed',
null=True, verbose_name=b'Upstream speed (Kbps)')),
('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name=b'Cross-connect ID')),
('pp_info', models.CharField(blank=True, max_length=100, verbose_name=b'Patch panel/port(s)')),
('circuit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations',
to='circuits.Circuit')),
('interface', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='circuit_termination', to='dcim.Interface')),
('site',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations',
to='dcim.Site')),
],
options={
'ordering': ['circuit', 'term_side'],
},
),
migrations.AlterUniqueTogether(
name='circuittermination',
unique_together=set([('circuit', 'term_side')]),
),
migrations.RunPython(circuits_to_terms, terms_to_circuits),
migrations.RemoveField(
model_name='circuit',
name='interface',
),
migrations.RemoveField(
model_name='circuit',
name='port_speed',
),
migrations.RemoveField(
model_name='circuit',
name='pp_info',
),
migrations.RemoveField(
model_name='circuit',
name='site',
),
migrations.RemoveField(
model_name='circuit',
name='upstream_speed',
),
migrations.RemoveField(
model_name='circuit',
name='xconnect_id',
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-17 20:08
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0006_terminations'),
]
operations = [
migrations.AddField(
model_name='circuit',
name='description',
field=models.CharField(blank=True, max_length=100),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-04-19 17:17
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('circuits', '0007_circuit_add_description'),
]
operations = [
migrations.AlterField(
model_name='circuittermination',
name='interface',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_termination', to='dcim.Interface'),
),
]

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-24 15:34
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0008_circuittermination_interface_protect_on_delete'),
]
operations = [
migrations.AlterField(
model_name='circuit',
name='cid',
field=models.CharField(max_length=50, verbose_name='Circuit ID'),
),
migrations.AlterField(
model_name='circuit',
name='commit_rate',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)'),
),
migrations.AlterField(
model_name='circuit',
name='install_date',
field=models.DateField(blank=True, null=True, verbose_name='Date installed'),
),
migrations.AlterField(
model_name='circuittermination',
name='port_speed',
field=models.PositiveIntegerField(verbose_name='Port speed (Kbps)'),
),
migrations.AlterField(
model_name='circuittermination',
name='pp_info',
field=models.CharField(blank=True, max_length=100, verbose_name='Patch panel/port(s)'),
),
migrations.AlterField(
model_name='circuittermination',
name='term_side',
field=models.CharField(choices=[('A', 'A'), ('Z', 'Z')], max_length=1, verbose_name='Termination'),
),
migrations.AlterField(
model_name='circuittermination',
name='upstream_speed',
field=models.PositiveIntegerField(blank=True, help_text='Upstream speed, if different from port speed', null=True, verbose_name='Upstream speed (Kbps)'),
),
migrations.AlterField(
model_name='circuittermination',
name='xconnect_id',
field=models.CharField(blank=True, max_length=50, verbose_name='Cross-connect ID'),
),
migrations.AlterField(
model_name='provider',
name='account',
field=models.CharField(blank=True, max_length=30, verbose_name='Account number'),
),
migrations.AlterField(
model_name='provider',
name='admin_contact',
field=models.TextField(blank=True, verbose_name='Admin contact'),
),
migrations.AlterField(
model_name='provider',
name='asn',
field=dcim.fields.ASNField(blank=True, null=True, verbose_name='ASN'),
),
migrations.AlterField(
model_name='provider',
name='noc_contact',
field=models.TextField(blank=True, verbose_name='NOC contact'),
),
migrations.AlterField(
model_name='provider',
name='portal_url',
field=models.URLField(blank=True, verbose_name='Portal'),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.9 on 2018-02-06 18:48
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0009_unicode_literals'),
]
operations = [
migrations.AddField(
model_name='circuit',
name='status',
field=models.PositiveSmallIntegerField(choices=[[2, 'Planned'], [3, 'Provisioning'], [1, 'Active'], [4, 'Offline'], [0, 'Deprovisioning'], [5, 'Decommissioned']], default=1),
),
]

View File

@@ -1,12 +1,20 @@
from django.core.urlresolvers import reverse
from __future__ import unicode_literals
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from dcim.constants import STATUS_CLASSES
from dcim.fields import ASNField
from dcim.models import Site, Interface
from extras.models import CustomFieldModel, CustomFieldValue
from tenancy.models import Tenant
from utilities.models import CreatedUpdatedModel
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
class Provider(CreatedUpdatedModel):
@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
stores information pertinent to the user's relationship with the Provider.
@@ -19,45 +27,61 @@ class Provider(CreatedUpdatedModel):
noc_contact = models.TextField(blank=True, verbose_name='NOC contact')
admin_contact = models.TextField(blank=True, verbose_name='Admin contact')
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', 'noc_contact', 'admin_contact', 'comments']
class Meta:
ordering = ['name']
def __unicode__(self):
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('circuits:provider', args=[self.slug])
def to_csv(self):
return ','.join([
return (
self.name,
self.slug,
str(self.asn) if self.asn else '',
self.asn,
self.account,
self.portal_url,
])
self.noc_contact,
self.admin_contact,
self.comments,
)
@python_2_unicode_compatible
class CircuitType(models.Model):
"""
Circuits can be orgnanized by their functional role. For example, a user might wish to define CircuitTypes named
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
"Long Haul," "Metro," or "Out-of-Band".
"""
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
csv_headers = ['name', 'slug']
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)
def to_csv(self):
return (
self.name,
self.slug,
)
class Circuit(CreatedUpdatedModel):
@python_2_unicode_compatible
class Circuit(CreatedUpdatedModel, CustomFieldModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
circuits. Each circuit is also assigned a CircuitType and a Site. A Circuit may be terminated to a specific device
@@ -66,59 +90,85 @@ class Circuit(CreatedUpdatedModel):
cid = models.CharField(max_length=50, verbose_name='Circuit ID')
provider = models.ForeignKey('Provider', related_name='circuits', on_delete=models.PROTECT)
type = models.ForeignKey('CircuitType', related_name='circuits', on_delete=models.PROTECT)
site = models.ForeignKey(Site, related_name='circuits', on_delete=models.PROTECT)
interface = models.OneToOneField(Interface, related_name='circuit', blank=True, null=True)
status = models.PositiveSmallIntegerField(choices=CIRCUIT_STATUS_CHOICES, default=CIRCUIT_STATUS_ACTIVE)
tenant = models.ForeignKey(Tenant, related_name='circuits', blank=True, null=True, on_delete=models.PROTECT)
install_date = models.DateField(blank=True, null=True, verbose_name='Date installed')
port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)')
xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID')
pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)')
description = models.CharField(max_length=100, blank=True)
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', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
]
class Meta:
ordering = ['provider', 'cid']
unique_together = ['provider', 'cid']
def __unicode__(self):
return u'{} {}'.format(self.provider, self.cid)
def __str__(self):
return '{} {}'.format(self.provider, self.cid)
def get_absolute_url(self):
return reverse('circuits:circuit', args=[self.pk])
def to_csv(self):
return ','.join([
return (
self.cid,
self.provider.name,
self.type.name,
self.site.name,
self.install_date.isoformat() if self.install_date else '',
str(self.port_speed),
str(self.commit_rate) if self.commit_rate else '',
self.xconnect_id,
self.pp_info,
])
self.get_status_display(),
self.tenant.name if self.tenant else None,
self.install_date,
self.commit_rate,
self.description,
self.comments,
)
def _humanize_speed(self, speed):
"""
Humanize speeds given in Kbps (e.g. 10000000 becomes '10 Gbps')
"""
if speed >= 1000000000 and speed % 1000000000 == 0:
return '{} Tbps'.format(speed / 1000000000)
elif speed >= 1000000 and speed % 1000000 == 0:
return '{} Gbps'.format(speed / 1000000)
elif speed >= 1000 and speed % 1000 == 0:
return '{} Mbps'.format(speed / 1000)
elif speed >= 1000:
return '{} Mbps'.format(float(speed) / 1000)
else:
return '{} Kbps'.format(speed)
def get_status_class(self):
return STATUS_CLASSES[self.status]
def _get_termination(self, side):
for ct in self.terminations.all():
if ct.term_side == side:
return ct
return None
@property
def port_speed_human(self):
return self._humanize_speed(self.port_speed)
def termination_a(self):
return self._get_termination('A')
@property
def commit_rate_human(self):
if not self.commit_rate:
return ''
return self._humanize_speed(self.commit_rate)
def termination_z(self):
return self._get_termination('Z')
@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')
site = models.ForeignKey('dcim.Site', related_name='circuit_terminations', on_delete=models.PROTECT)
interface = models.OneToOneField(
'dcim.Interface', related_name='circuit_termination', blank=True, null=True, on_delete=models.PROTECT
)
port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
upstream_speed = models.PositiveIntegerField(
blank=True, null=True, verbose_name='Upstream speed (Kbps)',
help_text='Upstream speed, if different from port speed'
)
xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID')
pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)')
class Meta:
ordering = ['circuit', 'term_side']
unique_together = ['circuit', 'term_side']
def __str__(self):
return '{} (Side {})'.format(self.circuit, self.get_term_side_display())
def get_peer_termination(self):
peer_side = 'Z' if self.term_side == 'A' else 'A'
try:
return CircuitTermination.objects.select_related('site').get(circuit=self.circuit, term_side=peer_side)
except CircuitTermination.DoesNotExist:
return None

View File

@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.utils import timezone
from .models import Circuit, CircuitTermination
@receiver((post_save, post_delete), sender=CircuitTermination)
def update_circuit(instance, **kwargs):
"""
When a CircuitTermination has been modified, update the last_updated time of its parent Circuit.
"""
Circuit.objects.filter(pk=instance.circuit_id).update(last_updated=timezone.now())

View File

@@ -1,17 +1,38 @@
from __future__ import unicode_literals
import django_tables2 as tables
from django.utils.safestring import mark_safe
from django_tables2.utils import Accessor
from tenancy.tables import COL_TENANT
from utilities.tables import BaseTable, ToggleColumn
from .models import Circuit, CircuitType, Provider
CIRCUITTYPE_EDIT_LINK = """
CIRCUITTYPE_ACTIONS = """
{% if perms.circuit.change_circuittype %}
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}">Edit</a>
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
{% endif %}
"""
STATUS_LABEL = """
<span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
"""
class CircuitTerminationColumn(tables.Column):
def render(self, value):
if value.interface:
return mark_safe('<a href="{}" title="{}">{}</a>'.format(
value.interface.device.get_absolute_url(),
value.site,
value.interface.device
))
return mark_safe('<a href="{}">{}</a>'.format(
value.site.get_absolute_url(),
value.site
))
#
# Providers
@@ -19,13 +40,19 @@ CIRCUITTYPE_EDIT_LINK = """
class ProviderTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn('circuits:provider', args=[Accessor('slug')], verbose_name='Name')
asn = tables.Column(verbose_name='ASN')
circuit_count = tables.Column(accessor=Accessor('count_circuits'), verbose_name='Circuits')
name = tables.LinkColumn()
class Meta(BaseTable.Meta):
model = Provider
fields = ('pk', 'name', 'asn', 'circuit_count')
fields = ('pk', 'name', 'asn', 'account',)
class ProviderDetailTable(ProviderTable):
circuit_count = tables.Column(accessor=Accessor('count_circuits'), verbose_name='Circuits')
class Meta(ProviderTable.Meta):
model = Provider
fields = ('pk', 'name', 'asn', 'account', 'circuit_count')
#
@@ -34,14 +61,15 @@ class ProviderTable(BaseTable):
class CircuitTypeTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
name = tables.LinkColumn()
circuit_count = tables.Column(verbose_name='Circuits')
slug = tables.Column(verbose_name='Slug')
edit = tables.TemplateColumn(template_code=CIRCUITTYPE_EDIT_LINK, verbose_name='')
actions = tables.TemplateColumn(
template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
)
class Meta(BaseTable.Meta):
model = CircuitType
fields = ('pk', 'name', 'circuit_count', 'slug', 'edit')
fields = ('pk', 'name', 'circuit_count', 'slug', 'actions')
#
@@ -50,13 +78,13 @@ class CircuitTypeTable(BaseTable):
class CircuitTable(BaseTable):
pk = ToggleColumn()
cid = tables.LinkColumn('circuits:circuit', args=[Accessor('pk')], verbose_name='ID')
type = tables.Column(verbose_name='Type')
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')], verbose_name='Provider')
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
port_speed_human = tables.Column(verbose_name='Port Speed')
commit_rate_human = tables.Column(verbose_name='Commit Rate')
cid = tables.LinkColumn(verbose_name='ID')
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side')
termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side')
class Meta(BaseTable.Meta):
model = Circuit
fields = ('pk', 'cid', 'type', 'provider', 'site', 'port_speed_human', 'commit_rate_human')
fields = ('pk', 'cid', 'status', 'type', 'provider', 'tenant', 'termination_a', 'termination_z', 'description')

View File

View File

@@ -0,0 +1,387 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from circuits.constants import TERM_SIDE_A, TERM_SIDE_Z
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.models import Site
from extras.constants import GRAPH_TYPE_PROVIDER
from extras.models import Graph
from users.models import Token
from utilities.tests import HttpStatusMixin
class ProviderTest(HttpStatusMixin, APITestCase):
def setUp(self):
user = User.objects.create(username='testuser', is_superuser=True)
token = Token.objects.create(user=user)
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
self.provider3 = Provider.objects.create(name='Test Provider 3', slug='test-provider-3')
def test_get_provider(self):
url = reverse('circuits-api:provider-detail', kwargs={'pk': self.provider1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['name'], self.provider1.name)
def test_get_provider_graphs(self):
self.graph1 = Graph.objects.create(
type=GRAPH_TYPE_PROVIDER, name='Test Graph 1',
source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=1'
)
self.graph2 = Graph.objects.create(
type=GRAPH_TYPE_PROVIDER, name='Test Graph 2',
source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=2'
)
self.graph3 = Graph.objects.create(
type=GRAPH_TYPE_PROVIDER, name='Test Graph 3',
source='http://example.com/graphs.py?provider={{ obj.slug }}&foo=3'
)
url = reverse('circuits-api:provider-graphs', kwargs={'pk': self.provider1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(len(response.data), 3)
self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?provider=test-provider-1&foo=1')
def test_list_providers(self):
url = reverse('circuits-api:provider-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_create_provider(self):
data = {
'name': 'Test Provider 4',
'slug': 'test-provider-4',
}
url = reverse('circuits-api:provider-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Provider.objects.count(), 4)
provider4 = Provider.objects.get(pk=response.data['id'])
self.assertEqual(provider4.name, data['name'])
self.assertEqual(provider4.slug, data['slug'])
def test_create_provider_bulk(self):
data = [
{
'name': 'Test Provider 4',
'slug': 'test-provider-4',
},
{
'name': 'Test Provider 5',
'slug': 'test-provider-5',
},
{
'name': 'Test Provider 6',
'slug': 'test-provider-6',
},
]
url = reverse('circuits-api:provider-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Provider.objects.count(), 6)
self.assertEqual(response.data[0]['name'], data[0]['name'])
self.assertEqual(response.data[1]['name'], data[1]['name'])
self.assertEqual(response.data[2]['name'], data[2]['name'])
def test_update_provider(self):
data = {
'name': 'Test Provider X',
'slug': 'test-provider-x',
}
url = reverse('circuits-api:provider-detail', kwargs={'pk': self.provider1.pk})
response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(Provider.objects.count(), 3)
provider1 = Provider.objects.get(pk=response.data['id'])
self.assertEqual(provider1.name, data['name'])
self.assertEqual(provider1.slug, data['slug'])
def test_delete_provider(self):
url = reverse('circuits-api:provider-detail', kwargs={'pk': self.provider1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(Provider.objects.count(), 2)
class CircuitTypeTest(HttpStatusMixin, APITestCase):
def setUp(self):
user = User.objects.create(username='testuser', is_superuser=True)
token = Token.objects.create(user=user)
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
self.circuittype1 = CircuitType.objects.create(name='Test Circuit Type 1', slug='test-circuit-type-1')
self.circuittype2 = CircuitType.objects.create(name='Test Circuit Type 2', slug='test-circuit-type-2')
self.circuittype3 = CircuitType.objects.create(name='Test Circuit Type 3', slug='test-circuit-type-3')
def test_get_circuittype(self):
url = reverse('circuits-api:circuittype-detail', kwargs={'pk': self.circuittype1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['name'], self.circuittype1.name)
def test_list_circuittypes(self):
url = reverse('circuits-api:circuittype-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_create_circuittype(self):
data = {
'name': 'Test Circuit Type 4',
'slug': 'test-circuit-type-4',
}
url = reverse('circuits-api:circuittype-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(CircuitType.objects.count(), 4)
circuittype4 = CircuitType.objects.get(pk=response.data['id'])
self.assertEqual(circuittype4.name, data['name'])
self.assertEqual(circuittype4.slug, data['slug'])
def test_update_circuittype(self):
data = {
'name': 'Test Circuit Type X',
'slug': 'test-circuit-type-x',
}
url = reverse('circuits-api:circuittype-detail', kwargs={'pk': self.circuittype1.pk})
response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(CircuitType.objects.count(), 3)
circuittype1 = CircuitType.objects.get(pk=response.data['id'])
self.assertEqual(circuittype1.name, data['name'])
self.assertEqual(circuittype1.slug, data['slug'])
def test_delete_circuittype(self):
url = reverse('circuits-api:circuittype-detail', kwargs={'pk': self.circuittype1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(CircuitType.objects.count(), 2)
class CircuitTest(HttpStatusMixin, APITestCase):
def setUp(self):
user = User.objects.create(username='testuser', is_superuser=True)
token = Token.objects.create(user=user)
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
self.circuittype1 = CircuitType.objects.create(name='Test Circuit Type 1', slug='test-circuit-type-1')
self.circuittype2 = CircuitType.objects.create(name='Test Circuit Type 2', slug='test-circuit-type-2')
self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=self.provider1, type=self.circuittype1)
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=self.provider1, type=self.circuittype1)
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=self.provider1, type=self.circuittype1)
def test_get_circuit(self):
url = reverse('circuits-api:circuit-detail', kwargs={'pk': self.circuit1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['cid'], self.circuit1.cid)
def test_list_circuits(self):
url = reverse('circuits-api:circuit-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_create_circuit(self):
data = {
'cid': 'TEST0004',
'provider': self.provider1.pk,
'type': self.circuittype1.pk,
}
url = reverse('circuits-api:circuit-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Circuit.objects.count(), 4)
circuit4 = Circuit.objects.get(pk=response.data['id'])
self.assertEqual(circuit4.cid, data['cid'])
self.assertEqual(circuit4.provider_id, data['provider'])
self.assertEqual(circuit4.type_id, data['type'])
def test_create_circuit_bulk(self):
data = [
{
'cid': 'TEST0004',
'provider': self.provider1.pk,
'type': self.circuittype1.pk,
},
{
'cid': 'TEST0005',
'provider': self.provider1.pk,
'type': self.circuittype1.pk,
},
{
'cid': 'TEST0006',
'provider': self.provider1.pk,
'type': self.circuittype1.pk,
},
]
url = reverse('circuits-api:circuit-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Circuit.objects.count(), 6)
self.assertEqual(response.data[0]['cid'], data[0]['cid'])
self.assertEqual(response.data[1]['cid'], data[1]['cid'])
self.assertEqual(response.data[2]['cid'], data[2]['cid'])
def test_update_circuit(self):
data = {
'cid': 'TEST000X',
'provider': self.provider2.pk,
'type': self.circuittype2.pk,
}
url = reverse('circuits-api:circuit-detail', kwargs={'pk': self.circuit1.pk})
response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(Circuit.objects.count(), 3)
circuit1 = Circuit.objects.get(pk=response.data['id'])
self.assertEqual(circuit1.cid, data['cid'])
self.assertEqual(circuit1.provider_id, data['provider'])
self.assertEqual(circuit1.type_id, data['type'])
def test_delete_circuit(self):
url = reverse('circuits-api:circuit-detail', kwargs={'pk': self.circuit1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(Circuit.objects.count(), 2)
class CircuitTerminationTest(HttpStatusMixin, APITestCase):
def setUp(self):
user = User.objects.create(username='testuser', is_superuser=True)
token = Token.objects.create(user=user)
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
provider = Provider.objects.create(name='Test Provider', slug='test-provider')
circuittype = CircuitType.objects.create(name='Test Circuit Type', slug='test-circuit-type')
self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=provider, type=circuittype)
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
self.circuittermination1 = CircuitTermination.objects.create(
circuit=self.circuit1, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
)
self.circuittermination2 = CircuitTermination.objects.create(
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
)
self.circuittermination3 = CircuitTermination.objects.create(
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
)
def test_get_circuittermination(self):
url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': self.circuittermination1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['id'], self.circuittermination1.pk)
def test_list_circuitterminations(self):
url = reverse('circuits-api:circuittermination-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_create_circuittermination(self):
data = {
'circuit': self.circuit1.pk,
'term_side': TERM_SIDE_Z,
'site': self.site2.pk,
'port_speed': 1000000,
}
url = reverse('circuits-api:circuittermination-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(CircuitTermination.objects.count(), 4)
circuittermination4 = CircuitTermination.objects.get(pk=response.data['id'])
self.assertEqual(circuittermination4.circuit_id, data['circuit'])
self.assertEqual(circuittermination4.term_side, data['term_side'])
self.assertEqual(circuittermination4.site_id, data['site'])
self.assertEqual(circuittermination4.port_speed, data['port_speed'])
def test_update_circuittermination(self):
data = {
'circuit': self.circuit1.pk,
'term_side': TERM_SIDE_Z,
'site': self.site2.pk,
'port_speed': 1000000,
}
url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': self.circuittermination1.pk})
response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(CircuitTermination.objects.count(), 3)
circuittermination1 = CircuitTermination.objects.get(pk=response.data['id'])
self.assertEqual(circuittermination1.circuit_id, data['circuit'])
self.assertEqual(circuittermination1.term_side, data['term_side'])
self.assertEqual(circuittermination1.site_id, data['site'])
self.assertEqual(circuittermination1.port_speed, data['port_speed'])
def test_delete_circuittermination(self):
url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': self.circuittermination1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(CircuitTermination.objects.count(), 2)

View File

@@ -1,34 +1,43 @@
from __future__ import unicode_literals
from django.conf.urls import url
from . import views
app_name = 'circuits'
urlpatterns = [
# Providers
url(r'^providers/$', views.ProviderListView.as_view(), name='provider_list'),
url(r'^providers/add/$', views.ProviderEditView.as_view(), name='provider_add'),
url(r'^providers/add/$', views.ProviderCreateView.as_view(), name='provider_add'),
url(r'^providers/import/$', views.ProviderBulkImportView.as_view(), name='provider_import'),
url(r'^providers/edit/$', views.ProviderBulkEditView.as_view(), name='provider_bulk_edit'),
url(r'^providers/delete/$', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'),
url(r'^providers/(?P<slug>[\w-]+)/$', views.provider, name='provider'),
url(r'^providers/(?P<slug>[\w-]+)/$', views.ProviderView.as_view(), name='provider'),
url(r'^providers/(?P<slug>[\w-]+)/edit/$', views.ProviderEditView.as_view(), name='provider_edit'),
url(r'^providers/(?P<slug>[\w-]+)/delete/$', views.ProviderDeleteView.as_view(), name='provider_delete'),
# Circuit types
url(r'^circuit-types/$', views.CircuitTypeListView.as_view(), name='circuittype_list'),
url(r'^circuit-types/add/$', views.CircuitTypeEditView.as_view(), name='circuittype_add'),
url(r'^circuit-types/add/$', views.CircuitTypeCreateView.as_view(), name='circuittype_add'),
url(r'^circuit-types/import/$', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'),
url(r'^circuit-types/delete/$', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'),
url(r'^circuit-types/(?P<slug>[\w-]+)/edit/$', views.CircuitTypeEditView.as_view(), name='circuittype_edit'),
# Circuits
url(r'^circuits/$', views.CircuitListView.as_view(), name='circuit_list'),
url(r'^circuits/add/$', views.CircuitEditView.as_view(), name='circuit_add'),
url(r'^circuits/add/$', views.CircuitCreateView.as_view(), name='circuit_add'),
url(r'^circuits/import/$', views.CircuitBulkImportView.as_view(), name='circuit_import'),
url(r'^circuits/edit/$', views.CircuitBulkEditView.as_view(), name='circuit_bulk_edit'),
url(r'^circuits/delete/$', views.CircuitBulkDeleteView.as_view(), name='circuit_bulk_delete'),
url(r'^circuits/(?P<pk>\d+)/$', views.circuit, name='circuit'),
url(r'^circuits/(?P<pk>\d+)/$', views.CircuitView.as_view(), name='circuit'),
url(r'^circuits/(?P<pk>\d+)/edit/$', views.CircuitEditView.as_view(), name='circuit_edit'),
url(r'^circuits/(?P<pk>\d+)/delete/$', views.CircuitDeleteView.as_view(), name='circuit_delete'),
url(r'^circuits/(?P<pk>\d+)/terminations/swap/$', views.circuit_terminations_swap, name='circuit_terminations_swap'),
# Circuit terminations
url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
url(r'^circuit-terminations/(?P<pk>\d+)/edit/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
url(r'^circuit-terminations/(?P<pk>\d+)/delete/$', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
]

View File

@@ -1,13 +1,22 @@
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count
from django.shortcuts import get_object_or_404, render
from __future__ import unicode_literals
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db import transaction
from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.generic import View
from extras.models import Graph, GRAPH_TYPE_PROVIDER
from utilities.forms import ConfirmationForm
from utilities.views import (
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
)
from . import filters, forms, tables
from .models import Circuit, CircuitType, Provider
from .constants import TERM_SIDE_A, TERM_SIDE_Z
from .models import Circuit, CircuitTermination, CircuitType, Provider
#
@@ -18,65 +27,69 @@ class ProviderListView(ObjectListView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
filter = filters.ProviderFilter
filter_form = forms.ProviderFilterForm
table = tables.ProviderTable
edit_permissions = ['circuits.change_provider', 'circuits.delete_provider']
table = tables.ProviderDetailTable
template_name = 'circuits/provider_list.html'
def provider(request, slug):
class ProviderView(View):
provider = get_object_or_404(Provider, slug=slug)
circuits = Circuit.objects.filter(provider=provider).select_related('site', 'interface__device')
def get(self, request, slug):
return render(request, 'circuits/provider.html', {
'provider': provider,
'circuits': circuits,
})
provider = get_object_or_404(Provider, slug=slug)
circuits = Circuit.objects.filter(provider=provider).select_related(
'type', 'tenant'
).prefetch_related(
'terminations__site'
)
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists()
return render(request, 'circuits/provider.html', {
'provider': provider,
'circuits': circuits,
'show_graphs': show_graphs,
})
class ProviderEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.change_provider'
class ProviderCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.add_provider'
model = Provider
form_class = forms.ProviderForm
model_form = forms.ProviderForm
template_name = 'circuits/provider_edit.html'
cancel_url = 'circuits:provider_list'
default_return_url = 'circuits:provider_list'
class ProviderEditView(ProviderCreateView):
permission_required = 'circuits.change_provider'
class ProviderDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'circuits.delete_provider'
model = Provider
redirect_url = 'circuits:provider_list'
default_return_url = 'circuits:provider_list'
class ProviderBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'circuits.add_provider'
form = forms.ProviderImportForm
model_form = forms.ProviderCSVForm
table = tables.ProviderTable
template_name = 'circuits/provider_import.html'
obj_list_url = 'circuits:provider_list'
default_return_url = 'circuits:provider_list'
class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'circuits.change_provider'
cls = Provider
filter = filters.ProviderFilter
table = tables.ProviderTable
form = forms.ProviderBulkEditForm
template_name = 'circuits/provider_bulk_edit.html'
default_redirect_url = 'circuits:provider_list'
def update_objects(self, pk_list, form):
fields_to_update = {}
for field in ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']:
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
default_return_url = 'circuits:provider_list'
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_provider'
cls = Provider
default_redirect_url = 'circuits:provider_list'
filter = filters.ProviderFilter
table = tables.ProviderTable
default_return_url = 'circuits:provider_list'
#
@@ -86,22 +99,35 @@ class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
class CircuitTypeListView(ObjectListView):
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits'))
table = tables.CircuitTypeTable
edit_permissions = ['circuits.change_circuittype', 'circuits.delete_circuittype']
template_name = 'circuits/circuittype_list.html'
class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.change_circuittype'
class CircuitTypeCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.add_circuittype'
model = CircuitType
form_class = forms.CircuitTypeForm
success_url = 'circuits:circuittype_list'
cancel_url = 'circuits:circuittype_list'
model_form = forms.CircuitTypeForm
def get_return_url(self, request, obj):
return reverse('circuits:circuittype_list')
class CircuitTypeEditView(CircuitTypeCreateView):
permission_required = 'circuits.change_circuittype'
class CircuitTypeBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'circuits.add_circuittype'
model_form = forms.CircuitTypeCSVForm
table = tables.CircuitTypeTable
default_return_url = 'circuits:circuittype_list'
class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_circuittype'
cls = CircuitType
default_redirect_url = 'circuits:circuittype_list'
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits'))
table = tables.CircuitTypeTable
default_return_url = 'circuits:circuittype_list'
#
@@ -109,64 +135,152 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
#
class CircuitListView(ObjectListView):
queryset = Circuit.objects.select_related('provider', 'type', 'site')
queryset = Circuit.objects.select_related(
'provider', 'type', 'tenant'
).prefetch_related(
'terminations__site', 'terminations__interface__device'
)
filter = filters.CircuitFilter
filter_form = forms.CircuitFilterForm
table = tables.CircuitTable
edit_permissions = ['circuits.change_circuit', 'circuits.delete_circuit']
template_name = 'circuits/circuit_list.html'
def circuit(request, pk):
class CircuitView(View):
circuit = get_object_or_404(Circuit, pk=pk)
def get(self, request, pk):
return render(request, 'circuits/circuit.html', {
'circuit': circuit,
})
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
termination_a = CircuitTermination.objects.select_related(
'site__region', 'interface__device'
).filter(
circuit=circuit, term_side=TERM_SIDE_A
).first()
termination_z = CircuitTermination.objects.select_related(
'site__region', 'interface__device'
).filter(
circuit=circuit, term_side=TERM_SIDE_Z
).first()
return render(request, 'circuits/circuit.html', {
'circuit': circuit,
'termination_a': termination_a,
'termination_z': termination_z,
})
class CircuitEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.change_circuit'
class CircuitCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.add_circuit'
model = Circuit
form_class = forms.CircuitForm
fields_initial = ['site']
model_form = forms.CircuitForm
template_name = 'circuits/circuit_edit.html'
cancel_url = 'circuits:circuit_list'
default_return_url = 'circuits:circuit_list'
class CircuitEditView(CircuitCreateView):
permission_required = 'circuits.change_circuit'
class CircuitDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'circuits.delete_circuit'
model = Circuit
redirect_url = 'circuits:circuit_list'
default_return_url = 'circuits:circuit_list'
class CircuitBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'circuits.add_circuit'
form = forms.CircuitImportForm
model_form = forms.CircuitCSVForm
table = tables.CircuitTable
template_name = 'circuits/circuit_import.html'
obj_list_url = 'circuits:circuit_list'
default_return_url = 'circuits:circuit_list'
class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'circuits.change_circuit'
cls = Circuit
queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
filter = filters.CircuitFilter
table = tables.CircuitTable
form = forms.CircuitBulkEditForm
template_name = 'circuits/circuit_bulk_edit.html'
default_redirect_url = 'circuits:circuit_list'
def update_objects(self, pk_list, form):
fields_to_update = {}
for field in ['type', 'provider', 'port_speed', 'commit_rate', 'comments']:
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
default_return_url = 'circuits:circuit_list'
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_circuit'
cls = Circuit
default_redirect_url = 'circuits:circuit_list'
queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
filter = filters.CircuitFilter
table = tables.CircuitTable
default_return_url = 'circuits:circuit_list'
@permission_required('circuits.change_circuittermination')
def circuit_terminations_swap(request, pk):
circuit = get_object_or_404(Circuit, pk=pk)
termination_a = CircuitTermination.objects.filter(circuit=circuit, term_side=TERM_SIDE_A).first()
termination_z = CircuitTermination.objects.filter(circuit=circuit, term_side=TERM_SIDE_Z).first()
if not termination_a and not termination_z:
messages.error(request, "No terminations have been defined for circuit {}.".format(circuit))
return redirect('circuits:circuit', pk=circuit.pk)
if request.method == 'POST':
form = ConfirmationForm(request.POST)
if form.is_valid():
if termination_a and termination_z:
# Use a placeholder to avoid an IntegrityError on the (circuit, term_side) unique constraint
with transaction.atomic():
termination_a.term_side = '_'
termination_a.save()
termination_z.term_side = 'A'
termination_z.save()
termination_a.term_side = 'Z'
termination_a.save()
elif termination_a:
termination_a.term_side = 'Z'
termination_a.save()
else:
termination_z.term_side = 'A'
termination_z.save()
messages.success(request, "Swapped terminations for circuit {}.".format(circuit))
return redirect('circuits:circuit', pk=circuit.pk)
else:
form = ConfirmationForm()
return render(request, 'circuits/circuit_terminations_swap.html', {
'circuit': circuit,
'termination_a': termination_a,
'termination_z': termination_z,
'form': form,
'panel_class': 'default',
'button_class': 'primary',
'return_url': circuit.get_absolute_url(),
})
#
# Circuit terminations
#
class CircuitTerminationCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.add_circuittermination'
model = CircuitTermination
model_form = forms.CircuitTerminationForm
template_name = 'circuits/circuittermination_edit.html'
def alter_obj(self, obj, request, url_args, url_kwargs):
if 'circuit' in url_kwargs:
obj.circuit = get_object_or_404(Circuit, pk=url_kwargs['circuit'])
return obj
def get_return_url(self, request, obj):
return obj.circuit.get_absolute_url()
class CircuitTerminationEditView(CircuitTerminationCreateView):
permission_required = 'circuits.change_circuittermination'
class CircuitTerminationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'circuits.delete_circuittermination'
model = CircuitTermination

View File

@@ -1,183 +0,0 @@
from django.contrib import admin
from django.db.models import Count
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Module, Platform,
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site,
)
@admin.register(Site)
class SiteAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'facility', 'asn']
prepopulated_fields = {
'slug': ['name'],
}
@admin.register(RackGroup)
class RackGroupAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'site']
prepopulated_fields = {
'slug': ['name'],
}
@admin.register(Rack)
class RackAdmin(admin.ModelAdmin):
list_display = ['name', 'facility_id', 'site', 'u_height']
#
# Device types
#
@admin.register(Manufacturer)
class ManufacturerAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['name'],
}
list_display = ['name', 'slug']
class ConsolePortTemplateAdmin(admin.TabularInline):
model = ConsolePortTemplate
class ConsoleServerPortTemplateAdmin(admin.TabularInline):
model = ConsoleServerPortTemplate
class PowerPortTemplateAdmin(admin.TabularInline):
model = PowerPortTemplate
class PowerOutletTemplateAdmin(admin.TabularInline):
model = PowerOutletTemplate
class InterfaceTemplateAdmin(admin.TabularInline):
model = InterfaceTemplate
class DeviceBayTemplateAdmin(admin.TabularInline):
model = DeviceBayTemplate
@admin.register(DeviceType)
class DeviceTypeAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['model'],
}
inlines = [
ConsolePortTemplateAdmin,
ConsoleServerPortTemplateAdmin,
PowerPortTemplateAdmin,
PowerOutletTemplateAdmin,
InterfaceTemplateAdmin,
DeviceBayTemplateAdmin,
]
list_display = ['model', 'manufacturer', 'slug', 'part_number', 'u_height', 'console_ports', 'console_server_ports',
'power_ports', 'power_outlets', 'interfaces', 'device_bays']
list_filter = ['manufacturer']
def get_queryset(self, request):
return DeviceType.objects.annotate(
console_port_count=Count('console_port_templates', distinct=True),
cs_port_count=Count('cs_port_templates', distinct=True),
power_port_count=Count('power_port_templates', distinct=True),
power_outlet_count=Count('power_outlet_templates', distinct=True),
interface_count=Count('interface_templates', distinct=True),
devicebay_count=Count('device_bay_templates', distinct=True),
)
def console_ports(self, instance):
return instance.console_port_count
def console_server_ports(self, instance):
return instance.cs_port_count
def power_ports(self, instance):
return instance.power_port_count
def power_outlets(self, instance):
return instance.power_outlet_count
def interfaces(self, instance):
return instance.interface_count
def device_bays(self, instance):
return instance.devicebay_count
#
# Devices
#
@admin.register(DeviceRole)
class DeviceRoleAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['name'],
}
list_display = ['name', 'slug', 'color']
@admin.register(Platform)
class PlatformAdmin(admin.ModelAdmin):
prepopulated_fields = {
'slug': ['name'],
}
list_display = ['name', 'rpc_client']
class ConsolePortAdmin(admin.TabularInline):
model = ConsolePort
readonly_fields = ['cs_port']
class ConsoleServerPortAdmin(admin.TabularInline):
model = ConsoleServerPort
class PowerPortAdmin(admin.TabularInline):
model = PowerPort
readonly_fields = ['power_outlet']
class PowerOutletAdmin(admin.TabularInline):
model = PowerOutlet
class InterfaceAdmin(admin.TabularInline):
model = Interface
class DeviceBayAdmin(admin.TabularInline):
model = DeviceBay
fk_name = 'device'
readonly_fields = ['installed_device']
class ModuleAdmin(admin.TabularInline):
model = Module
readonly_fields = ['parent', 'discovered']
@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
inlines = [
ConsolePortAdmin,
ConsoleServerPortAdmin,
PowerPortAdmin,
PowerOutletAdmin,
InterfaceAdmin,
DeviceBayAdmin,
ModuleAdmin,
]
list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'serial']
list_filter = ['device_role']
def get_queryset(self, request):
qs = super(DeviceAdmin, self).get_queryset(request)
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack')

View File

@@ -1,3 +1,5 @@
from __future__ import unicode_literals
from rest_framework.exceptions import APIException

View File

@@ -1,29 +1,94 @@
from rest_framework import serializers
from __future__ import unicode_literals
from ipam.models import IPAddress
from dcim.models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Platform, PowerOutlet,
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
from collections import OrderedDict
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from circuits.models import Circuit, CircuitTermination
from dcim.constants import (
CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES,
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
)
from dcim.models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, VirtualChassis,
)
from extras.api.customfields import CustomFieldModelSerializer
from ipam.models import IPAddress, VLAN
from tenancy.api.serializers import NestedTenantSerializer
from users.api.serializers import NestedUserSerializer
from utilities.api import ChoiceFieldSerializer, TimeZoneField, ValidatedModelSerializer
from virtualization.models import Cluster
#
# Regions
#
class NestedRegionSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
class Meta:
model = Region
fields = ['id', 'url', 'name', 'slug']
class RegionSerializer(serializers.ModelSerializer):
parent = NestedRegionSerializer()
class Meta:
model = Region
fields = ['id', 'name', 'slug', 'parent']
class WritableRegionSerializer(ValidatedModelSerializer):
class Meta:
model = Region
fields = ['id', 'name', 'slug', 'parent']
#
# Sites
#
class SiteSerializer(serializers.ModelSerializer):
class SiteSerializer(CustomFieldModelSerializer):
status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES)
region = NestedRegionSerializer()
tenant = NestedTenantSerializer()
time_zone = TimeZoneField(required=False)
class Meta:
model = Site
fields = ['id', 'name', 'slug', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments',
'count_prefixes', 'count_vlans', 'count_racks', 'count_devices', 'count_circuits']
fields = [
'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description',
'physical_address', 'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments',
'custom_fields', 'created', 'last_updated', 'count_prefixes', 'count_vlans', 'count_racks', 'count_devices',
'count_circuits',
]
class SiteNestedSerializer(SiteSerializer):
class NestedSiteSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
class Meta(SiteSerializer.Meta):
fields = ['id', 'name', 'slug']
class Meta:
model = Site
fields = ['id', 'url', 'name', 'slug']
class WritableSiteSerializer(CustomFieldModelSerializer):
time_zone = TimeZoneField(required=False)
class Meta:
model = Site
fields = [
'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description',
'physical_address', 'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments',
'custom_fields', 'created', 'last_updated',
]
#
@@ -31,159 +96,335 @@ class SiteNestedSerializer(SiteSerializer):
#
class RackGroupSerializer(serializers.ModelSerializer):
site = SiteNestedSerializer()
site = NestedSiteSerializer()
class Meta:
model = RackGroup
fields = ['id', 'name', 'slug', 'site']
class RackGroupNestedSerializer(RackGroupSerializer):
class NestedRackGroupSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
class Meta(SiteSerializer.Meta):
fields = ['id', 'name', 'slug']
class Meta:
model = RackGroup
fields = ['id', 'url', 'name', 'slug']
class WritableRackGroupSerializer(ValidatedModelSerializer):
class Meta:
model = RackGroup
fields = ['id', 'name', 'slug', 'site']
#
# Rack roles
#
class RackRoleSerializer(ValidatedModelSerializer):
class Meta:
model = RackRole
fields = ['id', 'name', 'slug', 'color']
class NestedRackRoleSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
class Meta:
model = RackRole
fields = ['id', 'url', 'name', 'slug']
#
# Racks
#
class RackSerializer(serializers.ModelSerializer):
site = SiteNestedSerializer()
group = RackGroupNestedSerializer()
class RackSerializer(CustomFieldModelSerializer):
site = NestedSiteSerializer()
group = NestedRackGroupSerializer()
tenant = NestedTenantSerializer()
role = NestedRackRoleSerializer()
type = ChoiceFieldSerializer(choices=RACK_TYPE_CHOICES)
width = ChoiceFieldSerializer(choices=RACK_WIDTH_CHOICES)
class Meta:
model = Rack
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'u_height', 'comments']
fields = [
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
'u_height', 'desc_units', 'comments', 'custom_fields', 'created', 'last_updated',
]
class RackNestedSerializer(RackSerializer):
class NestedRackSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
class Meta(RackSerializer.Meta):
fields = ['id', 'name', 'facility_id', 'display_name']
class Meta:
model = Rack
fields = ['id', 'url', 'name', 'display_name']
class RackDetailSerializer(RackSerializer):
front_units = serializers.SerializerMethodField()
rear_units = serializers.SerializerMethodField()
class WritableRackSerializer(CustomFieldModelSerializer):
class Meta(RackSerializer.Meta):
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'u_height', 'comments', 'front_units',
'rear_units']
class Meta:
model = Rack
fields = [
'id', 'name', 'facility_id', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width', 'u_height',
'desc_units', 'comments', 'custom_fields', 'created', 'last_updated',
]
# Omit the UniqueTogetherValidator that would be automatically added to validate (site, facility_id). This
# prevents facility_id from being interpreted as a required field.
validators = [
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('site', 'name'))
]
def get_front_units(self, obj):
units = obj.get_rack_units(face=RACK_FACE_FRONT)
for u in units:
u['device'] = DeviceNestedSerializer(u['device']).data if u['device'] else None
return units
def validate(self, data):
def get_rear_units(self, obj):
units = obj.get_rack_units(face=RACK_FACE_REAR)
for u in units:
u['device'] = DeviceNestedSerializer(u['device']).data if u['device'] else None
return units
# Validate uniqueness of (site, facility_id) since we omitted the automatically-created validator from Meta.
if data.get('facility_id', None):
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('site', 'facility_id'))
validator.set_context(self)
validator(data)
# Enforce model validation
super(WritableRackSerializer, self).validate(data)
return data
#
# Rack units
#
class NestedDeviceSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
class Meta:
model = Device
fields = ['id', 'url', 'name', 'display_name']
class RackUnitSerializer(serializers.Serializer):
"""
A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database.
"""
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(read_only=True)
face = serializers.IntegerField(read_only=True)
device = NestedDeviceSerializer(read_only=True)
#
# Rack reservations
#
class RackReservationSerializer(serializers.ModelSerializer):
rack = NestedRackSerializer()
user = NestedUserSerializer()
tenant = NestedTenantSerializer()
class Meta:
model = RackReservation
fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
class WritableRackReservationSerializer(ValidatedModelSerializer):
class Meta:
model = RackReservation
fields = ['id', 'rack', 'units', 'user', 'description']
#
# Manufacturers
#
class ManufacturerSerializer(serializers.ModelSerializer):
class ManufacturerSerializer(ValidatedModelSerializer):
class Meta:
model = Manufacturer
fields = ['id', 'name', 'slug']
class ManufacturerNestedSerializer(ManufacturerSerializer):
class NestedManufacturerSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
class Meta(ManufacturerSerializer.Meta):
pass
class Meta:
model = Manufacturer
fields = ['id', 'url', 'name', 'slug']
#
# Device types
#
class DeviceTypeSerializer(serializers.ModelSerializer):
manufacturer = ManufacturerNestedSerializer()
class DeviceTypeSerializer(CustomFieldModelSerializer):
manufacturer = NestedManufacturerSerializer()
interface_ordering = ChoiceFieldSerializer(choices=IFACE_ORDERING_CHOICES)
subdevice_role = ChoiceFieldSerializer(choices=SUBDEVICE_ROLE_CHOICES)
instance_count = serializers.IntegerField(source='instances.count', read_only=True)
class Meta:
model = DeviceType
fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
'is_console_server', 'is_pdu', 'is_network_device']
fields = [
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_fields',
'instance_count',
]
class DeviceTypeNestedSerializer(DeviceTypeSerializer):
class NestedDeviceTypeSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
manufacturer = NestedManufacturerSerializer()
class Meta(DeviceTypeSerializer.Meta):
fields = ['id', 'manufacturer', 'model', 'slug']
class Meta:
model = DeviceType
fields = ['id', 'url', 'manufacturer', 'model', 'slug']
class ConsolePortTemplateNestedSerializer(serializers.ModelSerializer):
class WritableDeviceTypeSerializer(CustomFieldModelSerializer):
class Meta:
model = DeviceType
fields = [
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_fields',
]
#
# Console port templates
#
class ConsolePortTemplateSerializer(serializers.ModelSerializer):
device_type = NestedDeviceTypeSerializer()
class Meta:
model = ConsolePortTemplate
fields = ['id', 'name']
fields = ['id', 'device_type', 'name']
class ConsoleServerPortTemplateNestedSerializer(serializers.ModelSerializer):
class WritableConsolePortTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = ConsolePortTemplate
fields = ['id', 'device_type', 'name']
#
# Console server port templates
#
class ConsoleServerPortTemplateSerializer(serializers.ModelSerializer):
device_type = NestedDeviceTypeSerializer()
class Meta:
model = ConsoleServerPortTemplate
fields = ['id', 'name']
fields = ['id', 'device_type', 'name']
class PowerPortTemplateNestedSerializer(serializers.ModelSerializer):
class WritableConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = ConsoleServerPortTemplate
fields = ['id', 'device_type', 'name']
#
# Power port templates
#
class PowerPortTemplateSerializer(serializers.ModelSerializer):
device_type = NestedDeviceTypeSerializer()
class Meta:
model = PowerPortTemplate
fields = ['id', 'name']
fields = ['id', 'device_type', 'name']
class PowerOutletTemplateNestedSerializer(serializers.ModelSerializer):
class WritablePowerPortTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = PowerPortTemplate
fields = ['id', 'device_type', 'name']
#
# Power outlet templates
#
class PowerOutletTemplateSerializer(serializers.ModelSerializer):
device_type = NestedDeviceTypeSerializer()
class Meta:
model = PowerOutletTemplate
fields = ['id', 'name']
fields = ['id', 'device_type', 'name']
class InterfaceTemplateNestedSerializer(serializers.ModelSerializer):
class WritablePowerOutletTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = PowerOutletTemplate
fields = ['id', 'device_type', 'name']
#
# Interface templates
#
class InterfaceTemplateSerializer(serializers.ModelSerializer):
device_type = NestedDeviceTypeSerializer()
form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES)
class Meta:
model = InterfaceTemplate
fields = ['id', 'name', 'form_factor', 'mgmt_only']
fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
class DeviceTypeDetailSerializer(DeviceTypeSerializer):
console_port_templates = ConsolePortTemplateNestedSerializer(many=True, read_only=True)
cs_port_templates = ConsoleServerPortTemplateNestedSerializer(many=True, read_only=True)
power_port_templates = PowerPortTemplateNestedSerializer(many=True, read_only=True)
power_outlet_templates = PowerPortTemplateNestedSerializer(many=True, read_only=True)
interface_templates = InterfaceTemplateNestedSerializer(many=True, read_only=True)
class WritableInterfaceTemplateSerializer(ValidatedModelSerializer):
class Meta(DeviceTypeSerializer.Meta):
fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
'is_console_server', 'is_pdu', 'is_network_device', 'console_port_templates', 'cs_port_templates',
'power_port_templates', 'power_outlet_templates', 'interface_templates']
class Meta:
model = InterfaceTemplate
fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
#
# Device bay templates
#
class DeviceBayTemplateSerializer(serializers.ModelSerializer):
device_type = NestedDeviceTypeSerializer()
class Meta:
model = DeviceBayTemplate
fields = ['id', 'device_type', 'name']
class WritableDeviceBayTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = DeviceBayTemplate
fields = ['id', 'device_type', 'name']
#
# Device roles
#
class DeviceRoleSerializer(serializers.ModelSerializer):
class DeviceRoleSerializer(ValidatedModelSerializer):
class Meta:
model = DeviceRole
fields = ['id', 'name', 'slug', 'color']
fields = ['id', 'name', 'slug', 'color', 'vm_role']
class DeviceRoleNestedSerializer(DeviceRoleSerializer):
class NestedDeviceRoleSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
class Meta(DeviceRoleSerializer.Meta):
fields = ['id', 'name', 'slug']
class Meta:
model = DeviceRole
fields = ['id', 'url', 'name', 'slug']
#
@@ -191,65 +432,119 @@ class DeviceRoleNestedSerializer(DeviceRoleSerializer):
#
class PlatformSerializer(serializers.ModelSerializer):
manufacturer = NestedManufacturerSerializer()
class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'rpc_client']
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
class PlatformNestedSerializer(PlatformSerializer):
class NestedPlatformSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
class Meta(PlatformSerializer.Meta):
fields = ['id', 'name', 'slug']
class Meta:
model = Platform
fields = ['id', 'url', 'name', 'slug']
class WritablePlatformSerializer(ValidatedModelSerializer):
class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
#
# Devices
#
# Cannot import ipam.api.IPAddressNestedSerializer due to circular dependency
class DeviceIPAddressNestedSerializer(serializers.ModelSerializer):
# Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
class DeviceIPAddressSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
class Meta:
model = IPAddress
fields = ['id', 'family', 'address']
fields = ['id', 'url', 'family', 'address']
class DeviceSerializer(serializers.ModelSerializer):
device_type = DeviceTypeNestedSerializer()
device_role = DeviceRoleNestedSerializer()
platform = PlatformNestedSerializer()
rack = RackNestedSerializer()
primary_ip = DeviceIPAddressNestedSerializer()
primary_ip4 = DeviceIPAddressNestedSerializer()
primary_ip6 = DeviceIPAddressNestedSerializer()
# Cannot import virtualization.api.NestedClusterSerializer due to circular dependency
class NestedClusterSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
class Meta:
model = Cluster
fields = ['id', 'url', 'name']
# Cannot import NestedVirtualChassisSerializer due to circular dependency
class DeviceVirtualChassisSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
master = NestedDeviceSerializer()
class Meta:
model = VirtualChassis
fields = ['id', 'url', 'master']
class DeviceSerializer(CustomFieldModelSerializer):
device_type = NestedDeviceTypeSerializer()
device_role = NestedDeviceRoleSerializer()
tenant = NestedTenantSerializer()
platform = NestedPlatformSerializer()
site = NestedSiteSerializer()
rack = NestedRackSerializer()
face = ChoiceFieldSerializer(choices=RACK_FACE_CHOICES)
status = ChoiceFieldSerializer(choices=DEVICE_STATUS_CHOICES)
primary_ip = DeviceIPAddressSerializer()
primary_ip4 = DeviceIPAddressSerializer()
primary_ip6 = DeviceIPAddressSerializer()
parent_device = serializers.SerializerMethodField()
cluster = NestedClusterSerializer()
virtual_chassis = DeviceVirtualChassisSerializer()
class Meta:
model = Device
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'platform', 'serial', 'rack', 'position',
'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
fields = [
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'custom_fields', 'created',
'last_updated',
]
def get_parent_device(self, obj):
try:
device_bay = obj.parent_bay
except DeviceBay.DoesNotExist:
return None
return {
'id': device_bay.device.pk,
'name': device_bay.device.name,
'device_bay': {
'id': device_bay.pk,
'name': device_bay.name,
}
}
context = {'request': self.context['request']}
data = NestedDeviceSerializer(instance=device_bay.device, context=context).data
data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
return data
class DeviceNestedSerializer(serializers.ModelSerializer):
class WritableDeviceSerializer(CustomFieldModelSerializer):
class Meta:
model = Device
fields = ['id', 'name', 'display_name']
fields = [
'id', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'rack',
'position', 'face', 'status', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
'vc_priority', 'comments', 'custom_fields', 'created', 'last_updated',
]
validators = []
def validate(self, data):
# Validate uniqueness of (rack, position, face) since we omitted the automatically-created validator from Meta.
if data.get('rack') and data.get('position') and data.get('face'):
validator = UniqueTogetherValidator(queryset=Device.objects.all(), fields=('rack', 'position', 'face'))
validator.set_context(self)
validator(data)
# Enforce model validation
super(WritableDeviceSerializer, self).validate(data)
return data
#
@@ -257,16 +552,18 @@ class DeviceNestedSerializer(serializers.ModelSerializer):
#
class ConsoleServerPortSerializer(serializers.ModelSerializer):
device = DeviceNestedSerializer()
device = NestedDeviceSerializer()
class Meta:
model = ConsoleServerPort
fields = ['id', 'device', 'name', 'connected_console']
read_only_fields = ['connected_console']
class ConsoleServerPortNestedSerializer(ConsoleServerPortSerializer):
class WritableConsoleServerPortSerializer(ValidatedModelSerializer):
class Meta(ConsoleServerPortSerializer.Meta):
class Meta:
model = ConsoleServerPort
fields = ['id', 'device', 'name']
@@ -275,18 +572,19 @@ class ConsoleServerPortNestedSerializer(ConsoleServerPortSerializer):
#
class ConsolePortSerializer(serializers.ModelSerializer):
device = DeviceNestedSerializer()
cs_port = ConsoleServerPortNestedSerializer()
device = NestedDeviceSerializer()
cs_port = ConsoleServerPortSerializer()
class Meta:
model = ConsolePort
fields = ['id', 'device', 'name', 'cs_port', 'connection_status']
class ConsolePortNestedSerializer(ConsolePortSerializer):
class WritableConsolePortSerializer(ValidatedModelSerializer):
class Meta(ConsolePortSerializer.Meta):
fields = ['id', 'device', 'name']
class Meta:
model = ConsolePort
fields = ['id', 'device', 'name', 'cs_port', 'connection_status']
#
@@ -294,16 +592,18 @@ class ConsolePortNestedSerializer(ConsolePortSerializer):
#
class PowerOutletSerializer(serializers.ModelSerializer):
device = DeviceNestedSerializer()
device = NestedDeviceSerializer()
class Meta:
model = PowerOutlet
fields = ['id', 'device', 'name', 'connected_port']
read_only_fields = ['connected_port']
class PowerOutletNestedSerializer(PowerOutletSerializer):
class WritablePowerOutletSerializer(ValidatedModelSerializer):
class Meta(PowerOutletSerializer.Meta):
class Meta:
model = PowerOutlet
fields = ['id', 'device', 'name']
@@ -312,46 +612,141 @@ class PowerOutletNestedSerializer(PowerOutletSerializer):
#
class PowerPortSerializer(serializers.ModelSerializer):
device = DeviceNestedSerializer()
power_outlet = PowerOutletNestedSerializer()
device = NestedDeviceSerializer()
power_outlet = PowerOutletSerializer()
class Meta:
model = PowerPort
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status']
class PowerPortNestedSerializer(PowerPortSerializer):
class WritablePowerPortSerializer(ValidatedModelSerializer):
class Meta(PowerPortSerializer.Meta):
fields = ['id', 'device', 'name']
class Meta:
model = PowerPort
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status']
#
# Interfaces
#
class InterfaceSerializer(serializers.ModelSerializer):
device = DeviceNestedSerializer()
form_factor = serializers.ReadOnlyField(source='get_form_factor_display')
class NestedInterfaceSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
class Meta:
model = Interface
fields = ['id', 'device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description', 'is_connected']
fields = ['id', 'url', 'name']
class InterfaceNestedSerializer(InterfaceSerializer):
form_factor = serializers.ReadOnlyField(source='get_form_factor_display')
class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
class Meta(InterfaceSerializer.Meta):
fields = ['id', 'device', 'name']
class Meta:
model = Circuit
fields = ['id', 'url', 'cid']
class InterfaceDetailSerializer(InterfaceSerializer):
connected_interface = InterfaceSerializer(source='get_connected_interface')
class InterfaceCircuitTerminationSerializer(serializers.ModelSerializer):
circuit = InterfaceNestedCircuitSerializer()
class Meta(InterfaceSerializer.Meta):
fields = ['id', 'device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description', 'is_connected',
'connected_interface']
class Meta:
model = CircuitTermination
fields = [
'id', 'circuit', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
]
# Cannot import ipam.api.NestedVLANSerializer due to circular dependency
class InterfaceVLANSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
class Meta:
model = VLAN
fields = ['id', 'url', 'vid', 'name', 'display_name']
class InterfaceSerializer(serializers.ModelSerializer):
device = NestedDeviceSerializer()
form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES)
lag = NestedInterfaceSerializer()
is_connected = serializers.SerializerMethodField(read_only=True)
interface_connection = serializers.SerializerMethodField(read_only=True)
circuit_termination = InterfaceCircuitTerminationSerializer()
untagged_vlan = InterfaceVLANSerializer()
mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES)
tagged_vlans = InterfaceVLANSerializer(many=True)
class Meta:
model = Interface
fields = [
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
]
def get_is_connected(self, obj):
"""
Return True if the interface has a connected interface or circuit termination.
"""
if obj.connection:
return True
try:
circuit_termination = obj.circuit_termination
return True
except CircuitTermination.DoesNotExist:
pass
return False
def get_interface_connection(self, obj):
if obj.connection:
return OrderedDict((
('interface', PeerInterfaceSerializer(obj.connected_interface, context=self.context).data),
('status', obj.connection.connection_status),
))
return None
class PeerInterfaceSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
device = NestedDeviceSerializer()
form_factor = ChoiceFieldSerializer(choices=IFACE_FF_CHOICES)
lag = NestedInterfaceSerializer()
class Meta:
model = Interface
fields = [
'id', 'url', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only',
'description',
]
class WritableInterfaceSerializer(ValidatedModelSerializer):
class Meta:
model = Interface
fields = [
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
'mode', 'untagged_vlan', 'tagged_vlans',
]
def validate(self, data):
# All associated VLANs be global or assigned to the parent device's site.
device = self.instance.device if self.instance else data.get('device')
untagged_vlan = data.get('untagged_vlan')
if untagged_vlan and untagged_vlan.site not in [device.site, None]:
raise serializers.ValidationError({
'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be "
"global.".format(untagged_vlan)
})
for vlan in data.get('tagged_vlans', []):
if vlan.site not in [device.site, None]:
raise serializers.ValidationError({
'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must "
"be global.".format(vlan)
})
return super(WritableInterfaceSerializer, self).validate(data)
#
@@ -359,33 +754,108 @@ class InterfaceDetailSerializer(InterfaceSerializer):
#
class DeviceBaySerializer(serializers.ModelSerializer):
device = DeviceNestedSerializer()
device = NestedDeviceSerializer()
installed_device = NestedDeviceSerializer()
class Meta:
model = DeviceBay
fields = ['id', 'device', 'name']
class DeviceBayNestedSerializer(DeviceBaySerializer):
installed_device = DeviceNestedSerializer()
class Meta(DeviceBaySerializer.Meta):
fields = ['id', 'name', 'installed_device']
class DeviceBayDetailSerializer(DeviceBaySerializer):
installed_device = DeviceNestedSerializer()
class Meta(DeviceBaySerializer.Meta):
fields = ['id', 'device', 'name', 'installed_device']
class NestedDeviceBaySerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
class Meta:
model = DeviceBay
fields = ['id', 'url', 'name']
class WritableDeviceBaySerializer(ValidatedModelSerializer):
class Meta:
model = DeviceBay
fields = ['id', 'device', 'name', 'installed_device']
#
# Inventory items
#
class InventoryItemSerializer(serializers.ModelSerializer):
device = NestedDeviceSerializer()
manufacturer = NestedManufacturerSerializer()
class Meta:
model = InventoryItem
fields = [
'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
'description',
]
class WritableInventoryItemSerializer(ValidatedModelSerializer):
# Provide a default value to satisfy UniqueTogetherValidator
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
class Meta:
model = InventoryItem
fields = [
'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
'description',
]
#
# Interface connections
#
class InterfaceConnectionSerializer(serializers.ModelSerializer):
interface_a = PeerInterfaceSerializer()
interface_b = PeerInterfaceSerializer()
connection_status = ChoiceFieldSerializer(choices=CONNECTION_STATUS_CHOICES)
class Meta:
model = InterfaceConnection
fields = ['id', 'interface_a', 'interface_b', 'connection_status']
class NestedInterfaceConnectionSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
class Meta:
model = InterfaceConnection
fields = ['id', 'url', 'connection_status']
class WritableInterfaceConnectionSerializer(ValidatedModelSerializer):
class Meta:
model = InterfaceConnection
fields = ['id', 'interface_a', 'interface_b', 'connection_status']
#
# Virtual chassis
#
class VirtualChassisSerializer(serializers.ModelSerializer):
master = NestedDeviceSerializer()
class Meta:
model = VirtualChassis
fields = ['id', 'master', 'domain']
class NestedVirtualChassisSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
class Meta:
model = VirtualChassis
fields = ['id', 'url']
class WritableVirtualChassisSerializer(ValidatedModelSerializer):
class Meta:
model = VirtualChassis
fields = ['id', 'master', 'domain']

View File

@@ -1,71 +1,70 @@
from django.conf.urls import url
from __future__ import unicode_literals
from extras.models import GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from extras.api.views import GraphListView, TopologyMapView
from rest_framework import routers
from .views import *
from . import views
urlpatterns = [
class DCIMRootView(routers.APIRootView):
"""
DCIM API root view
"""
def get_view_name(self):
return 'DCIM'
# Sites
url(r'^sites/$', SiteListView.as_view(), name='site_list'),
url(r'^sites/(?P<pk>\d+)/$', SiteDetailView.as_view(), name='site_detail'),
url(r'^sites/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_SITE}, name='site_graphs'),
url(r'^sites/(?P<site>\d+)/racks/$', RackListView.as_view(), name='site_racks'),
# Rack groups
url(r'^rack-groups/$', RackGroupListView.as_view(), name='rackgroup_list'),
url(r'^rack-groups/(?P<pk>\d+)/$', RackGroupDetailView.as_view(), name='rackgroup_detail'),
router = routers.DefaultRouter()
router.APIRootView = DCIMRootView
# Racks
url(r'^racks/$', RackListView.as_view(), name='rack_list'),
url(r'^racks/(?P<pk>\d+)/$', RackDetailView.as_view(), name='rack_detail'),
url(r'^racks/(?P<pk>\d+)/rack-units/$', RackUnitListView.as_view(), name='rack_units'),
# Field choices
router.register(r'_choices', views.DCIMFieldChoicesViewSet, base_name='field-choice')
# Manufacturers
url(r'^manufacturers/$', ManufacturerListView.as_view(), name='manufacturer_list'),
url(r'^manufacturers/(?P<pk>\d+)/$', ManufacturerDetailView.as_view(), name='manufacturer_detail'),
# Sites
router.register(r'regions', views.RegionViewSet)
router.register(r'sites', views.SiteViewSet)
# Device types
url(r'^device-types/$', DeviceTypeListView.as_view(), name='devicetype_list'),
url(r'^device-types/(?P<pk>\d+)/$', DeviceTypeDetailView.as_view(), name='devicetype_detail'),
# Racks
router.register(r'rack-groups', views.RackGroupViewSet)
router.register(r'rack-roles', views.RackRoleViewSet)
router.register(r'racks', views.RackViewSet)
router.register(r'rack-reservations', views.RackReservationViewSet)
# Device roles
url(r'^device-roles/$', DeviceRoleListView.as_view(), name='devicerole_list'),
url(r'^device-roles/(?P<pk>\d+)/$', DeviceRoleDetailView.as_view(), name='devicerole_detail'),
# Device types
router.register(r'manufacturers', views.ManufacturerViewSet)
router.register(r'device-types', views.DeviceTypeViewSet)
# Platforms
url(r'^platforms/$', PlatformListView.as_view(), name='platform_list'),
url(r'^platforms/(?P<pk>\d+)/$', PlatformDetailView.as_view(), name='platform_detail'),
# Device type components
router.register(r'console-port-templates', views.ConsolePortTemplateViewSet)
router.register(r'console-server-port-templates', views.ConsoleServerPortTemplateViewSet)
router.register(r'power-port-templates', views.PowerPortTemplateViewSet)
router.register(r'power-outlet-templates', views.PowerOutletTemplateViewSet)
router.register(r'interface-templates', views.InterfaceTemplateViewSet)
router.register(r'device-bay-templates', views.DeviceBayTemplateViewSet)
# Devices
url(r'^devices/$', DeviceListView.as_view(), name='device_list'),
url(r'^devices/(?P<pk>\d+)/$', DeviceDetailView.as_view(), name='device_detail'),
url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', LLDPNeighborsView.as_view(), name='device_lldp-neighbors'),
url(r'^devices/(?P<pk>\d+)/console-ports/$', ConsolePortListView.as_view(), name='device_consoleports'),
url(r'^devices/(?P<pk>\d+)/console-server-ports/$', ConsoleServerPortListView.as_view(),
name='device_consoleserverports'),
url(r'^devices/(?P<pk>\d+)/power-ports/$', PowerPortListView.as_view(), name='device_powerports'),
url(r'^devices/(?P<pk>\d+)/power-outlets/$', PowerOutletListView.as_view(), name='device_poweroutlets'),
url(r'^devices/(?P<pk>\d+)/interfaces/$', InterfaceListView.as_view(), name='device_interfaces'),
url(r'^devices/(?P<pk>\d+)/device-bays/$', DeviceBayListView.as_view(), name='device_devicebays'),
# Devices
router.register(r'device-roles', views.DeviceRoleViewSet)
router.register(r'platforms', views.PlatformViewSet)
router.register(r'devices', views.DeviceViewSet)
# Console ports
url(r'^console-ports/(?P<pk>\d+)/$', ConsolePortView.as_view(), name='consoleport'),
# Device components
router.register(r'console-ports', views.ConsolePortViewSet)
router.register(r'console-server-ports', views.ConsoleServerPortViewSet)
router.register(r'power-ports', views.PowerPortViewSet)
router.register(r'power-outlets', views.PowerOutletViewSet)
router.register(r'interfaces', views.InterfaceViewSet)
router.register(r'device-bays', views.DeviceBayViewSet)
router.register(r'inventory-items', views.InventoryItemViewSet)
# Power ports
url(r'^power-ports/(?P<pk>\d+)/$', PowerPortView.as_view(), name='powerport'),
# Connections
router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections')
router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections')
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
# Interfaces
url(r'^interfaces/(?P<pk>\d+)/$', InterfaceDetailView.as_view(), name='interface_detail'),
url(r'^interfaces/(?P<pk>\d+)/graphs/$', GraphListView.as_view(), {'type': GRAPH_TYPE_INTERFACE},
name='interface_graphs'),
url(r'^interface-connections/$', InterfaceConnectionListView.as_view(), name='interfaceconnection_list'),
url(r'^interface-connections/(?P<pk>\d+)/$', InterfaceConnectionView.as_view(), name='interfaceconnection_detail'),
# Virtual chassis
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
# Miscellaneous
url(r'^related-connections/$', RelatedConnectionsView.as_view(), name='related_connections'),
url(r'^topology-maps/(?P<slug>[\w-]+)/$', TopologyMapView.as_view(), name='topology_map'),
# Miscellaneous
router.register(r'connected-device', views.ConnectedDeviceViewSet, base_name='connected-device')
]
app_name = 'dcim-api'
urlpatterns = router.urls

View File

@@ -1,454 +1,448 @@
from rest_framework import generics
from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from __future__ import unicode_literals
from collections import OrderedDict
from django.conf import settings
from django.http import Http404
from django.db import transaction
from django.http import HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from drf_yasg.openapi import Parameter
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import detail_route
from rest_framework.mixins import ListModelMixin
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ViewSet
from dcim.models import (
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, IFACE_FF_VIRTUAL, Interface,
InterfaceConnection, Manufacturer, Platform, PowerOutlet, PowerPort, Rack, RackGroup, Site,
)
from dcim import filters
from .exceptions import MissingFilterException
from dcim.models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, VirtualChassis,
)
from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable
from . import serializers
from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer
from utilities.api import ServiceUnavailable
from .exceptions import MissingFilterException
#
# Field choices
#
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(Device, ['face', 'status']),
(ConsolePort, ['connection_status']),
(Interface, ['form_factor']),
(InterfaceConnection, ['connection_status']),
(InterfaceTemplate, ['form_factor']),
(PowerPort, ['connection_status']),
(Rack, ['type', 'width']),
)
#
# Regions
#
class RegionViewSet(ModelViewSet):
queryset = Region.objects.all()
serializer_class = serializers.RegionSerializer
write_serializer_class = serializers.WritableRegionSerializer
filter_class = filters.RegionFilter
#
# Sites
#
class SiteListView(generics.ListAPIView):
"""
List all sites
"""
queryset = Site.objects.all()
class SiteViewSet(CustomFieldModelViewSet):
queryset = Site.objects.select_related('region', 'tenant')
serializer_class = serializers.SiteSerializer
write_serializer_class = serializers.WritableSiteSerializer
filter_class = filters.SiteFilter
class SiteDetailView(generics.RetrieveAPIView):
"""
Retrieve a single site
"""
queryset = Site.objects.all()
serializer_class = serializers.SiteSerializer
@detail_route()
def graphs(self, request, pk=None):
"""
A convenience method for rendering graphs for a particular site.
"""
site = get_object_or_404(Site, pk=pk)
queryset = Graph.objects.filter(type=GRAPH_TYPE_SITE)
serializer = RenderedGraphSerializer(queryset, many=True, context={'graphed_object': site})
return Response(serializer.data)
#
# Rack groups
#
class RackGroupListView(generics.ListAPIView):
"""
List all rack groups
"""
queryset = RackGroup.objects.all()
class RackGroupViewSet(ModelViewSet):
queryset = RackGroup.objects.select_related('site')
serializer_class = serializers.RackGroupSerializer
write_serializer_class = serializers.WritableRackGroupSerializer
filter_class = filters.RackGroupFilter
class RackGroupDetailView(generics.RetrieveAPIView):
"""
Retrieve a single rack group
"""
queryset = RackGroup.objects.all()
serializer_class = serializers.RackGroupSerializer
#
# Rack roles
#
class RackRoleViewSet(ModelViewSet):
queryset = RackRole.objects.all()
serializer_class = serializers.RackRoleSerializer
filter_class = filters.RackRoleFilter
#
# Racks
#
class RackListView(generics.ListAPIView):
"""
List racks (filterable)
"""
queryset = Rack.objects.select_related('site')
class RackViewSet(CustomFieldModelViewSet):
queryset = Rack.objects.select_related('site', 'group__site', 'tenant')
serializer_class = serializers.RackSerializer
write_serializer_class = serializers.WritableRackSerializer
filter_class = filters.RackFilter
class RackDetailView(generics.RetrieveAPIView):
"""
Retrieve a single rack
"""
queryset = Rack.objects.select_related('site')
serializer_class = serializers.RackDetailSerializer
#
# Rack units
#
class RackUnitListView(APIView):
"""
List rack units (by rack)
"""
def get(self, request, pk):
@detail_route()
def units(self, request, pk=None):
"""
List rack units (by rack)
"""
rack = get_object_or_404(Rack, pk=pk)
face = request.GET.get('face', 0)
elevation = rack.get_rack_units(face)
exclude_pk = request.GET.get('exclude', None)
if exclude_pk is not None:
try:
exclude_pk = int(exclude_pk)
except ValueError:
exclude_pk = None
elevation = rack.get_rack_units(face, exclude_pk)
# Serialize Devices within the rack elevation
for u in elevation:
if u['device']:
u['device'] = serializers.DeviceNestedSerializer(instance=u['device']).data
page = self.paginate_queryset(elevation)
if page is not None:
rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
return self.get_paginated_response(rack_units.data)
return Response(elevation)
#
# Rack reservations
#
class RackReservationViewSet(ModelViewSet):
queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
serializer_class = serializers.RackReservationSerializer
write_serializer_class = serializers.WritableRackReservationSerializer
filter_class = filters.RackReservationFilter
# Assign user from request
def perform_create(self, serializer):
serializer.save(user=self.request.user)
#
# Manufacturers
#
class ManufacturerListView(generics.ListAPIView):
"""
List all hardware manufacturers
"""
queryset = Manufacturer.objects.all()
serializer_class = serializers.ManufacturerSerializer
class ManufacturerDetailView(generics.RetrieveAPIView):
"""
Retrieve a single hardware manufacturers
"""
class ManufacturerViewSet(ModelViewSet):
queryset = Manufacturer.objects.all()
serializer_class = serializers.ManufacturerSerializer
filter_class = filters.ManufacturerFilter
#
# Device Types
# Device types
#
class DeviceTypeListView(generics.ListAPIView):
"""
List device types (filterable)
"""
class DeviceTypeViewSet(CustomFieldModelViewSet):
queryset = DeviceType.objects.select_related('manufacturer')
serializer_class = serializers.DeviceTypeSerializer
write_serializer_class = serializers.WritableDeviceTypeSerializer
filter_class = filters.DeviceTypeFilter
class DeviceTypeDetailView(generics.RetrieveAPIView):
"""
Retrieve a single device type
"""
queryset = DeviceType.objects.select_related('manufacturer')
serializer_class = serializers.DeviceTypeDetailSerializer
#
# Device type components
#
class ConsolePortTemplateViewSet(ModelViewSet):
queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.ConsolePortTemplateSerializer
write_serializer_class = serializers.WritableConsolePortTemplateSerializer
filter_class = filters.ConsolePortTemplateFilter
class ConsoleServerPortTemplateViewSet(ModelViewSet):
queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.ConsoleServerPortTemplateSerializer
write_serializer_class = serializers.WritableConsoleServerPortTemplateSerializer
filter_class = filters.ConsoleServerPortTemplateFilter
class PowerPortTemplateViewSet(ModelViewSet):
queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.PowerPortTemplateSerializer
write_serializer_class = serializers.WritablePowerPortTemplateSerializer
filter_class = filters.PowerPortTemplateFilter
class PowerOutletTemplateViewSet(ModelViewSet):
queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.PowerOutletTemplateSerializer
write_serializer_class = serializers.WritablePowerOutletTemplateSerializer
filter_class = filters.PowerOutletTemplateFilter
class InterfaceTemplateViewSet(ModelViewSet):
queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.InterfaceTemplateSerializer
write_serializer_class = serializers.WritableInterfaceTemplateSerializer
filter_class = filters.InterfaceTemplateFilter
class DeviceBayTemplateViewSet(ModelViewSet):
queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.DeviceBayTemplateSerializer
write_serializer_class = serializers.WritableDeviceBayTemplateSerializer
filter_class = filters.DeviceBayTemplateFilter
#
# Device roles
#
class DeviceRoleListView(generics.ListAPIView):
"""
List all device roles
"""
queryset = DeviceRole.objects.all()
serializer_class = serializers.DeviceRoleSerializer
class DeviceRoleDetailView(generics.RetrieveAPIView):
"""
Retrieve a single device role
"""
class DeviceRoleViewSet(ModelViewSet):
queryset = DeviceRole.objects.all()
serializer_class = serializers.DeviceRoleSerializer
filter_class = filters.DeviceRoleFilter
#
# Platforms
#
class PlatformListView(generics.ListAPIView):
"""
List all platforms
"""
queryset = Platform.objects.all()
serializer_class = serializers.PlatformSerializer
class PlatformDetailView(generics.RetrieveAPIView):
"""
Retrieve a single platform
"""
class PlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
serializer_class = serializers.PlatformSerializer
write_serializer_class = serializers.WritablePlatformSerializer
filter_class = filters.PlatformFilter
#
# Devices
#
class DeviceListView(generics.ListAPIView):
"""
List devices (filterable)
"""
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'platform', 'rack__site')\
.prefetch_related('primary_ip4__nat_outside', 'primary_ip6__nat_outside')
class DeviceViewSet(CustomFieldModelViewSet):
queryset = Device.objects.select_related(
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
'virtual_chassis__master',
).prefetch_related(
'primary_ip4__nat_outside', 'primary_ip6__nat_outside',
)
serializer_class = serializers.DeviceSerializer
write_serializer_class = serializers.WritableDeviceSerializer
filter_class = filters.DeviceFilter
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]
class DeviceDetailView(generics.RetrieveAPIView):
"""
Retrieve a single device
"""
queryset = Device.objects.all()
serializer_class = serializers.DeviceSerializer
#
# Console ports
#
class ConsolePortListView(generics.ListAPIView):
"""
List console ports (by device)
"""
serializer_class = serializers.ConsolePortSerializer
def get_queryset(self):
device = get_object_or_404(Device, pk=self.kwargs['pk'])
return ConsolePort.objects.filter(device=device).select_related('cs_port')
class ConsolePortView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
serializer_class = serializers.ConsolePortSerializer
queryset = ConsolePort.objects.all()
#
# Console server ports
#
class ConsoleServerPortListView(generics.ListAPIView):
"""
List console server ports (by device)
"""
serializer_class = serializers.ConsoleServerPortSerializer
def get_queryset(self):
device = get_object_or_404(Device, pk=self.kwargs['pk'])
return ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
#
# Power ports
#
class PowerPortListView(generics.ListAPIView):
"""
List power ports (by device)
"""
serializer_class = serializers.PowerPortSerializer
def get_queryset(self):
device = get_object_or_404(Device, pk=self.kwargs['pk'])
return PowerPort.objects.filter(device=device).select_related('power_outlet')
class PowerPortView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
serializer_class = serializers.PowerPortSerializer
queryset = PowerPort.objects.all()
#
# Power outlets
#
class PowerOutletListView(generics.ListAPIView):
"""
List power outlets (by device)
"""
serializer_class = serializers.PowerOutletSerializer
def get_queryset(self):
device = get_object_or_404(Device, pk=self.kwargs['pk'])
return PowerOutlet.objects.filter(device=device).select_related('connected_port')
#
# Interfaces
#
class InterfaceListView(generics.ListAPIView):
"""
List interfaces (by device)
"""
serializer_class = serializers.InterfaceSerializer
filter_class = filters.InterfaceFilter
def get_queryset(self):
device = get_object_or_404(Device, pk=self.kwargs['pk'])
queryset = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b')
# Filter by type (physical or virtual)
iface_type = self.request.query_params.get('type')
if iface_type == 'physical':
queryset = queryset.exclude(form_factor=IFACE_FF_VIRTUAL)
elif iface_type == 'virtual':
queryset = queryset.filter(form_factor=IFACE_FF_VIRTUAL)
elif iface_type is not None:
queryset = queryset.empty()
return queryset
class InterfaceDetailView(generics.RetrieveAPIView):
"""
Retrieve a single interface
"""
queryset = Interface.objects.select_related('device')
serializer_class = serializers.InterfaceDetailSerializer
class InterfaceConnectionView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
serializer_class = serializers.InterfaceConnectionSerializer
queryset = InterfaceConnection.objects.all()
class InterfaceConnectionListView(generics.ListAPIView):
"""
Retrieve a list of all interface connections
"""
serializer_class = serializers.InterfaceConnectionSerializer
queryset = InterfaceConnection.objects.all()
#
# Device bays
#
class DeviceBayListView(generics.ListAPIView):
"""
List device bays (by device)
"""
serializer_class = serializers.DeviceBayNestedSerializer
def get_queryset(self):
device = get_object_or_404(Device, pk=self.kwargs['pk'])
queryset = DeviceBay.objects.filter(device=device).select_related('installed_device')
# Filter by type (physical or virtual)
iface_type = self.request.query_params.get('type')
if iface_type == 'physical':
queryset = queryset.exclude(form_factor=IFACE_FF_VIRTUAL)
elif iface_type == 'virtual':
queryset = queryset.filter(form_factor=IFACE_FF_VIRTUAL)
elif iface_type is not None:
queryset = queryset.empty()
return queryset
#
# Live queries
#
class LLDPNeighborsView(APIView):
"""
Retrieve live LLDP neighbors of a device
"""
def get(self, request, pk):
@detail_route(url_path='napalm')
def napalm(self, request, pk):
"""
Execute a NAPALM method on a Device
"""
device = get_object_or_404(Device, pk=pk)
if not device.primary_ip:
raise ServiceUnavailable(detail="No IP configured for this device.")
raise ServiceUnavailable("This device does not have a primary IP address configured.")
if device.platform is None:
raise ServiceUnavailable("No platform is configured for this device.")
if not device.platform.napalm_driver:
raise ServiceUnavailable("No NAPALM driver is configured for this device's platform ().".format(
device.platform
))
RPC = device.get_rpc_client()
if not RPC:
raise ServiceUnavailable(detail="No RPC client available for this platform ({}).".format(device.platform))
# Connect to device and retrieve inventory info
# Check that NAPALM is installed
try:
with RPC(device, username=settings.NETBOX_USERNAME, password=settings.NETBOX_PASSWORD) as rpc_client:
lldp_neighbors = rpc_client.get_lldp_neighbors()
except:
raise ServiceUnavailable(detail="Error connecting to the remote device.")
import napalm
except ImportError:
raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.")
from napalm.base.exceptions import ConnectAuthError, ModuleImportError
return Response(lldp_neighbors)
# Validate the configured driver
try:
driver = napalm.get_network_driver(device.platform.napalm_driver)
except ModuleImportError:
raise ServiceUnavailable("NAPALM driver for platform {} not found: {}.".format(
device.platform, device.platform.napalm_driver
))
# Verify user permission
if not request.user.has_perm('dcim.napalm_read'):
return HttpResponseForbidden()
# Validate requested NAPALM methods
napalm_methods = request.GET.getlist('method')
for method in napalm_methods:
if not hasattr(driver, method):
return HttpResponseBadRequest("Unknown NAPALM method: {}".format(method))
elif not method.startswith('get_'):
return HttpResponseBadRequest("Unsupported NAPALM method: {}".format(method))
# Connect to the device and execute the requested methods
# TODO: Improve error handling
response = OrderedDict([(m, None) for m in napalm_methods])
ip_address = str(device.primary_ip.address.ip)
d = driver(
hostname=ip_address,
username=settings.NAPALM_USERNAME,
password=settings.NAPALM_PASSWORD,
timeout=settings.NAPALM_TIMEOUT,
optional_args=settings.NAPALM_ARGS
)
try:
d.open()
for method in napalm_methods:
response[method] = getattr(d, method)()
except Exception as e:
raise ServiceUnavailable("Error connecting to the device at {}: {}".format(ip_address, e))
d.close()
return Response(response)
#
# Device components
#
class ConsolePortViewSet(ModelViewSet):
queryset = ConsolePort.objects.select_related('device', 'cs_port__device')
serializer_class = serializers.ConsolePortSerializer
write_serializer_class = serializers.WritableConsolePortSerializer
filter_class = filters.ConsolePortFilter
class ConsoleServerPortViewSet(ModelViewSet):
queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device')
serializer_class = serializers.ConsoleServerPortSerializer
write_serializer_class = serializers.WritableConsoleServerPortSerializer
filter_class = filters.ConsoleServerPortFilter
class PowerPortViewSet(ModelViewSet):
queryset = PowerPort.objects.select_related('device', 'power_outlet__device')
serializer_class = serializers.PowerPortSerializer
write_serializer_class = serializers.WritablePowerPortSerializer
filter_class = filters.PowerPortFilter
class PowerOutletViewSet(ModelViewSet):
queryset = PowerOutlet.objects.select_related('device', 'connected_port__device')
serializer_class = serializers.PowerOutletSerializer
write_serializer_class = serializers.WritablePowerOutletSerializer
filter_class = filters.PowerOutletFilter
class InterfaceViewSet(ModelViewSet):
queryset = Interface.objects.select_related('device')
serializer_class = serializers.InterfaceSerializer
write_serializer_class = serializers.WritableInterfaceSerializer
filter_class = filters.InterfaceFilter
@detail_route()
def graphs(self, request, pk=None):
"""
A convenience method for rendering graphs for a particular interface.
"""
interface = get_object_or_404(Interface, pk=pk)
queryset = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE)
serializer = RenderedGraphSerializer(queryset, many=True, context={'graphed_object': interface})
return Response(serializer.data)
class DeviceBayViewSet(ModelViewSet):
queryset = DeviceBay.objects.select_related('installed_device')
serializer_class = serializers.DeviceBaySerializer
write_serializer_class = serializers.WritableDeviceBaySerializer
filter_class = filters.DeviceBayFilter
class InventoryItemViewSet(ModelViewSet):
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
serializer_class = serializers.InventoryItemSerializer
write_serializer_class = serializers.WritableInventoryItemSerializer
filter_class = filters.InventoryItemFilter
#
# Connections
#
class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)
serializer_class = serializers.ConsolePortSerializer
filter_class = filters.ConsoleConnectionFilter
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)
serializer_class = serializers.PowerPortSerializer
filter_class = filters.PowerConnectionFilter
class InterfaceConnectionViewSet(ModelViewSet):
queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
serializer_class = serializers.InterfaceConnectionSerializer
write_serializer_class = serializers.WritableInterfaceConnectionSerializer
filter_class = filters.InterfaceConnectionFilter
#
# Virtual chassis
#
class VirtualChassisViewSet(ModelViewSet):
queryset = VirtualChassis.objects.all()
serializer_class = serializers.VirtualChassisSerializer
write_serializer_class = serializers.WritableVirtualChassisSerializer
#
# Miscellaneous
#
class RelatedConnectionsView(APIView):
class ConnectedDeviceViewSet(ViewSet):
"""
Retrieve all connections related to a given console/power/interface connection
This endpoint allows a user to determine what device (if any) is connected to a given peer device and peer
interface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors
via a protocol such as LLDP. Two query parameters must be included in the request:
* `peer-device`: The name of the peer device
* `peer-interface`: The name of the peer interface
"""
permission_classes = [IsAuthenticatedOrLoginNotRequired]
_device_param = Parameter('peer-device', 'query',
description='The name of the peer device', required=True, type=openapi.TYPE_STRING)
_interface_param = Parameter('peer-interface', 'query',
description='The name of the peer interface', required=True, type=openapi.TYPE_STRING)
def get(self, request):
def get_view_name(self):
return "Connected Device Locator"
peer_device = request.GET.get('peer-device')
peer_interface = request.GET.get('peer-interface')
@swagger_auto_schema(
manual_parameters=[_device_param, _interface_param], responses={'200': serializers.DeviceSerializer})
def list(self, request):
# Search by interface
if peer_device and peer_interface:
peer_device_name = request.query_params.get(self._device_param.name)
peer_interface_name = request.query_params.get(self._interface_param.name)
if not peer_device_name or not peer_interface_name:
raise MissingFilterException(detail='Request must include "peer-device" and "peer-interface" filters.')
# Determine local interface from peer interface's connection
try:
peer_iface = Interface.objects.get(device__name=peer_device, name=peer_interface)
except Interface.DoesNotExist:
raise Http404()
local_iface = peer_iface.get_connected_interface()
if local_iface:
device = local_iface.device
else:
return Response()
# Determine local interface from peer interface's connection
peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name)
local_interface = peer_interface.connected_interface
else:
raise MissingFilterException(detail='Must specify search parameters "peer-device" and "peer-interface".')
if local_interface is None:
return Response()
# Initialize response skeleton
response = {
'device': serializers.DeviceSerializer(device).data,
'console-ports': [],
'power-ports': [],
'interfaces': [],
}
# Console connections
console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
for cp in console_ports:
data = serializers.ConsolePortSerializer(instance=cp).data
del(data['device'])
response['console-ports'].append(data)
# Power connections
power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
for pp in power_ports:
data = serializers.PowerPortSerializer(instance=pp).data
del(data['device'])
response['power-ports'].append(data)
# Interface connections
interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b',
'circuit')
for iface in interfaces:
data = serializers.InterfaceDetailSerializer(instance=iface).data
del(data['device'])
response['interfaces'].append(data)
return Response(response)
return Response(serializers.DeviceSerializer(local_interface.device, context={'request': request}).data)

View File

@@ -1,6 +1,11 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class DCIMConfig(AppConfig):
name = "dcim"
verbose_name = "DCIM"
def ready(self):
import dcim.signals

257
netbox/dcim/constants.py Normal file
View File

@@ -0,0 +1,257 @@
from __future__ import unicode_literals
# Rack types
RACK_TYPE_2POST = 100
RACK_TYPE_4POST = 200
RACK_TYPE_CABINET = 300
RACK_TYPE_WALLFRAME = 1000
RACK_TYPE_WALLCABINET = 1100
RACK_TYPE_CHOICES = (
(RACK_TYPE_2POST, '2-post frame'),
(RACK_TYPE_4POST, '4-post frame'),
(RACK_TYPE_CABINET, '4-post cabinet'),
(RACK_TYPE_WALLFRAME, 'Wall-mounted frame'),
(RACK_TYPE_WALLCABINET, 'Wall-mounted cabinet'),
)
# Rack widths
RACK_WIDTH_19IN = 19
RACK_WIDTH_23IN = 23
RACK_WIDTH_CHOICES = (
(RACK_WIDTH_19IN, '19 inches'),
(RACK_WIDTH_23IN, '23 inches'),
)
# Rack faces
RACK_FACE_FRONT = 0
RACK_FACE_REAR = 1
RACK_FACE_CHOICES = [
[RACK_FACE_FRONT, 'Front'],
[RACK_FACE_REAR, 'Rear'],
]
# Parent/child device roles
SUBDEVICE_ROLE_PARENT = True
SUBDEVICE_ROLE_CHILD = False
SUBDEVICE_ROLE_CHOICES = (
(None, 'None'),
(SUBDEVICE_ROLE_PARENT, 'Parent'),
(SUBDEVICE_ROLE_CHILD, 'Child'),
)
# Interface ordering schemes (for device types)
IFACE_ORDERING_POSITION = 1
IFACE_ORDERING_NAME = 2
IFACE_ORDERING_CHOICES = [
[IFACE_ORDERING_POSITION, 'Slot/position'],
[IFACE_ORDERING_NAME, 'Name (alphabetically)']
]
# Interface form factors
# Virtual
IFACE_FF_VIRTUAL = 0
IFACE_FF_LAG = 200
# Ethernet
IFACE_FF_100ME_FIXED = 800
IFACE_FF_1GE_FIXED = 1000
IFACE_FF_1GE_GBIC = 1050
IFACE_FF_1GE_SFP = 1100
IFACE_FF_10GE_FIXED = 1150
IFACE_FF_10GE_CX4 = 1170
IFACE_FF_10GE_SFP_PLUS = 1200
IFACE_FF_10GE_XFP = 1300
IFACE_FF_10GE_XENPAK = 1310
IFACE_FF_10GE_X2 = 1320
IFACE_FF_25GE_SFP28 = 1350
IFACE_FF_40GE_QSFP_PLUS = 1400
IFACE_FF_100GE_CFP = 1500
IFACE_FF_100GE_CFP2 = 1510
IFACE_FF_100GE_CFP4 = 1520
IFACE_FF_100GE_CPAK = 1550
IFACE_FF_100GE_QSFP28 = 1600
# Wireless
IFACE_FF_80211A = 2600
IFACE_FF_80211G = 2610
IFACE_FF_80211N = 2620
IFACE_FF_80211AC = 2630
IFACE_FF_80211AD = 2640
# Fibrechannel
IFACE_FF_1GFC_SFP = 3010
IFACE_FF_2GFC_SFP = 3020
IFACE_FF_4GFC_SFP = 3040
IFACE_FF_8GFC_SFP_PLUS = 3080
IFACE_FF_16GFC_SFP_PLUS = 3160
# Serial
IFACE_FF_T1 = 4000
IFACE_FF_E1 = 4010
IFACE_FF_T3 = 4040
IFACE_FF_E3 = 4050
# Stacking
IFACE_FF_STACKWISE = 5000
IFACE_FF_STACKWISE_PLUS = 5050
IFACE_FF_FLEXSTACK = 5100
IFACE_FF_FLEXSTACK_PLUS = 5150
IFACE_FF_JUNIPER_VCP = 5200
# Other
IFACE_FF_OTHER = 32767
IFACE_FF_CHOICES = [
[
'Virtual interfaces',
[
[IFACE_FF_VIRTUAL, 'Virtual'],
[IFACE_FF_LAG, 'Link Aggregation Group (LAG)'],
],
],
[
'Ethernet (fixed)',
[
[IFACE_FF_100ME_FIXED, '100BASE-TX (10/100ME)'],
[IFACE_FF_1GE_FIXED, '1000BASE-T (1GE)'],
[IFACE_FF_10GE_FIXED, '10GBASE-T (10GE)'],
[IFACE_FF_10GE_CX4, '10GBASE-CX4 (10GE)'],
]
],
[
'Ethernet (modular)',
[
[IFACE_FF_1GE_GBIC, 'GBIC (1GE)'],
[IFACE_FF_1GE_SFP, 'SFP (1GE)'],
[IFACE_FF_10GE_SFP_PLUS, 'SFP+ (10GE)'],
[IFACE_FF_10GE_XFP, 'XFP (10GE)'],
[IFACE_FF_10GE_XENPAK, 'XENPAK (10GE)'],
[IFACE_FF_10GE_X2, 'X2 (10GE)'],
[IFACE_FF_25GE_SFP28, 'SFP28 (25GE)'],
[IFACE_FF_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
[IFACE_FF_100GE_CFP, 'CFP (100GE)'],
[IFACE_FF_100GE_CFP2, 'CFP2 (100GE)'],
[IFACE_FF_100GE_CFP4, 'CFP4 (100GE)'],
[IFACE_FF_100GE_CPAK, 'Cisco CPAK (100GE)'],
[IFACE_FF_100GE_QSFP28, 'QSFP28 (100GE)'],
]
],
[
'Wireless',
[
[IFACE_FF_80211A, 'IEEE 802.11a'],
[IFACE_FF_80211G, 'IEEE 802.11b/g'],
[IFACE_FF_80211N, 'IEEE 802.11n'],
[IFACE_FF_80211AC, 'IEEE 802.11ac'],
[IFACE_FF_80211AD, 'IEEE 802.11ad'],
]
],
[
'FibreChannel',
[
[IFACE_FF_1GFC_SFP, 'SFP (1GFC)'],
[IFACE_FF_2GFC_SFP, 'SFP (2GFC)'],
[IFACE_FF_4GFC_SFP, 'SFP (4GFC)'],
[IFACE_FF_8GFC_SFP_PLUS, 'SFP+ (8GFC)'],
[IFACE_FF_16GFC_SFP_PLUS, 'SFP+ (16GFC)'],
]
],
[
'Serial',
[
[IFACE_FF_T1, 'T1 (1.544 Mbps)'],
[IFACE_FF_E1, 'E1 (2.048 Mbps)'],
[IFACE_FF_T3, 'T3 (45 Mbps)'],
[IFACE_FF_E3, 'E3 (34 Mbps)'],
]
],
[
'Stacking',
[
[IFACE_FF_STACKWISE, 'Cisco StackWise'],
[IFACE_FF_STACKWISE_PLUS, 'Cisco StackWise Plus'],
[IFACE_FF_FLEXSTACK, 'Cisco FlexStack'],
[IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'],
[IFACE_FF_JUNIPER_VCP, 'Juniper VCP'],
]
],
[
'Other',
[
[IFACE_FF_OTHER, 'Other'],
]
],
]
VIRTUAL_IFACE_TYPES = [
IFACE_FF_VIRTUAL,
IFACE_FF_LAG,
]
WIRELESS_IFACE_TYPES = [
IFACE_FF_80211A,
IFACE_FF_80211G,
IFACE_FF_80211N,
IFACE_FF_80211AC,
IFACE_FF_80211AD,
]
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
IFACE_MODE_ACCESS = 100
IFACE_MODE_TAGGED = 200
IFACE_MODE_TAGGED_ALL = 300
IFACE_MODE_CHOICES = [
[IFACE_MODE_ACCESS, 'Access'],
[IFACE_MODE_TAGGED, 'Tagged'],
[IFACE_MODE_TAGGED_ALL, 'Tagged All'],
]
# Device statuses
DEVICE_STATUS_OFFLINE = 0
DEVICE_STATUS_ACTIVE = 1
DEVICE_STATUS_PLANNED = 2
DEVICE_STATUS_STAGED = 3
DEVICE_STATUS_FAILED = 4
DEVICE_STATUS_INVENTORY = 5
DEVICE_STATUS_CHOICES = [
[DEVICE_STATUS_ACTIVE, 'Active'],
[DEVICE_STATUS_OFFLINE, 'Offline'],
[DEVICE_STATUS_PLANNED, 'Planned'],
[DEVICE_STATUS_STAGED, 'Staged'],
[DEVICE_STATUS_FAILED, 'Failed'],
[DEVICE_STATUS_INVENTORY, 'Inventory'],
]
# Site statuses
SITE_STATUS_ACTIVE = 1
SITE_STATUS_PLANNED = 2
SITE_STATUS_RETIRED = 4
SITE_STATUS_CHOICES = [
[SITE_STATUS_ACTIVE, 'Active'],
[SITE_STATUS_PLANNED, 'Planned'],
[SITE_STATUS_RETIRED, 'Retired'],
]
# Bootstrap CSS classes for device statuses
STATUS_CLASSES = {
0: 'warning',
1: 'success',
2: 'info',
3: 'primary',
4: 'danger',
5: 'default',
}
# Console/power/interface connection statuses
CONNECTION_STATUS_PLANNED = False
CONNECTION_STATUS_CONNECTED = True
CONNECTION_STATUS_CHOICES = [
[CONNECTION_STATUS_PLANNED, 'Planned'],
[CONNECTION_STATUS_CONNECTED, 'Connected'],
]
# Platform -> RPC client mappings
RPC_CLIENT_JUNIPER_JUNOS = 'juniper-junos'
RPC_CLIENT_CISCO_IOS = 'cisco-ios'
RPC_CLIENT_OPENGEAR = 'opengear'
RPC_CLIENT_CHOICES = [
[RPC_CLIENT_JUNIPER_JUNOS, 'Juniper Junos (NETCONF)'],
[RPC_CLIENT_CISCO_IOS, 'Cisco IOS (SSH)'],
[RPC_CLIENT_OPENGEAR, 'Opengear (SSH)'],
]

View File

@@ -1,3 +1,5 @@
from __future__ import unicode_literals
from netaddr import EUI, mac_unix_expanded
from django.core.exceptions import ValidationError

View File

@@ -1,29 +1,108 @@
from __future__ import unicode_literals
import django_filters
from django.contrib.auth.models import User
from django.db.models import Q
from netaddr import EUI
from netaddr.core import AddrFormatError
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableCharFieldFilter, NumericInFilter
from virtualization.models import Cluster
from .constants import (
DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES,
WIRELESS_IFACE_TYPES,
)
from .models import (
ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer,
Platform, PowerOutlet, PowerPort, Rack, RackGroup, Site,
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, VirtualChassis,
)
class SiteFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',
class RegionFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
label='Parent region (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
name='parent__slug',
queryset=Region.objects.all(),
to_field_name='slug',
label='Parent region (slug)',
)
class Meta:
model = Region
fields = ['name', 'slug']
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(slug__icontains=value)
)
return queryset.filter(qs_filter)
class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
status = django_filters.MultipleChoiceFilter(
choices=SITE_STATUS_CHOICES,
null_value=None
)
region_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
label='Region (ID)',
)
region = django_filters.ModelMultipleChoiceFilter(
name='region__slug',
queryset=Region.objects.all(),
to_field_name='slug',
label='Region (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
class Meta:
model = Site
fields = ['q', 'name', 'facility', 'asn']
fields = ['q', 'name', 'slug', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email']
def search(self, queryset, value):
value = value.strip()
qs_filter = Q(name__icontains=value) | Q(facility__icontains=value) | Q(physical_address__icontains=value) | \
Q(shipping_address__icontains=value)
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(facility__icontains=value) |
Q(description__icontains=value) |
Q(physical_address__icontains=value) |
Q(shipping_address__icontains=value) |
Q(contact_name__icontains=value) |
Q(contact_phone__icontains=value) |
Q(contact_email__icontains=value) |
Q(comments__icontains=value)
)
try:
qs_filter |= Q(asn=int(value))
qs_filter |= Q(asn=int(value.strip()))
except ValueError:
pass
return queryset.filter(qs_filter)
@@ -31,12 +110,11 @@ class SiteFilter(django_filters.FilterSet):
class RackGroupFilter(django_filters.FilterSet):
site_id = django_filters.ModelMultipleChoiceFilter(
name='site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site',
name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -44,57 +122,166 @@ class RackGroupFilter(django_filters.FilterSet):
class Meta:
model = RackGroup
fields = ['site_id', 'site']
fields = ['site_id', 'name', 'slug']
class RackFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',
class RackRoleFilter(django_filters.FilterSet):
class Meta:
model = RackRole
fields = ['name', 'slug', 'color']
class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
facility_id = NullableCharFieldFilter()
site_id = django_filters.ModelMultipleChoiceFilter(
name='site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site',
name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
group_id = django_filters.ModelMultipleChoiceFilter(
name='group',
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = django_filters.ModelMultipleChoiceFilter(
name='group',
name='group__slug',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
name='role__slug',
queryset=RackRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
class Meta:
model = Rack
fields = ['q', 'site_id', 'site', 'u_height']
fields = ['name', 'serial', 'type', 'width', 'u_height', 'desc_units']
def search(self, queryset, value):
value = value.strip()
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(facility_id__icontains=value)
Q(facility_id__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(comments__icontains=value)
)
class DeviceTypeFilter(django_filters.FilterSet):
class RackReservationFilter(django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
rack_id = django_filters.ModelMultipleChoiceFilter(
queryset=Rack.objects.all(),
label='Rack (ID)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='rack__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='rack__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group',
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = django_filters.ModelMultipleChoiceFilter(
name='rack__group__slug',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
user_id = django_filters.ModelMultipleChoiceFilter(
queryset=User.objects.all(),
label='User (ID)',
)
user = django_filters.ModelMultipleChoiceFilter(
name='user',
queryset=User.objects.all(),
to_field_name='username',
label='User (name)',
)
class Meta:
model = RackReservation
fields = ['created']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(rack__name__icontains=value) |
Q(rack__facility_id__icontains=value) |
Q(user__username__icontains=value) |
Q(description__icontains=value)
)
class ManufacturerFilter(django_filters.FilterSet):
class Meta:
model = Manufacturer
fields = ['name', 'slug']
class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer',
name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
@@ -102,22 +289,157 @@ class DeviceTypeFilter(django_filters.FilterSet):
class Meta:
model = DeviceType
fields = ['manufacturer_id', 'manufacturer', 'model', 'part_number', 'u_height', 'is_console_server', 'is_pdu',
'is_network_device']
fields = [
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
'is_network_device', 'subdevice_role',
]
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(manufacturer__name__icontains=value) |
Q(model__icontains=value) |
Q(part_number__icontains=value) |
Q(comments__icontains=value)
)
class DeviceFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
devicetype_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceType.objects.all(),
name='device_type_id',
label='Device type (ID)',
)
class ConsolePortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = ConsolePortTemplate
fields = ['name']
class ConsoleServerPortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = ConsoleServerPortTemplate
fields = ['name']
class PowerPortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = PowerPortTemplate
fields = ['name']
class PowerOutletTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = PowerOutletTemplate
fields = ['name']
class InterfaceTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = InterfaceTemplate
fields = ['name', 'form_factor', 'mgmt_only']
class DeviceBayTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = DeviceBayTemplate
fields = ['name']
class DeviceRoleFilter(django_filters.FilterSet):
class Meta:
model = DeviceRole
fields = ['name', 'slug', 'color', 'vm_role']
class PlatformFilter(django_filters.FilterSet):
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
class Meta:
model = Platform
fields = ['name', 'slug']
class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='device_type__manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='device_type__manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
device_type_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceType.objects.all(),
label='Device type (ID)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
name='device_role_id',
queryset=DeviceRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
name='device_role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(),
label='Platform (ID)',
)
platform = django_filters.ModelMultipleChoiceFilter(
name='platform__slug',
queryset=Platform.objects.all(),
to_field_name='slug',
label='Platform (slug)',
)
name = NullableCharFieldFilter()
asset_tag = NullableCharFieldFilter()
site_id = django_filters.ModelMultipleChoiceFilter(
name='rack__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='rack__site',
name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site name (slug)',
@@ -132,53 +454,23 @@ class DeviceFilter(django_filters.FilterSet):
queryset=Rack.objects.all(),
label='Rack (ID)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
name='device_role',
queryset=DeviceRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
name='device_role',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
device_type_id = django_filters.ModelMultipleChoiceFilter(
name='device_type',
queryset=DeviceType.objects.all(),
label='Device type (ID)',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='device_type__manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='device_type__manufacturer',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(),
label='VM cluster (ID)',
)
model = django_filters.ModelMultipleChoiceFilter(
name='device_type',
name='device_type__slug',
queryset=DeviceType.objects.all(),
to_field_name='slug',
label='Device model (slug)',
)
platform_id = django_filters.ModelMultipleChoiceFilter(
name='platform',
queryset=Platform.objects.all(),
label='Platform (ID)',
status = django_filters.MultipleChoiceFilter(
choices=DEVICE_STATUS_CHOICES,
null_value=None
)
platform = django_filters.ModelMultipleChoiceFilter(
name='platform',
queryset=Platform.objects.all(),
to_field_name='slug',
label='Platform (slug)',
)
status = django_filters.BooleanFilter(
name='status',
label='Status',
is_full_depth = django_filters.BooleanFilter(
name='device_type__is_full_depth',
label='Is full depth',
)
is_console_server = django_filters.BooleanFilter(
name='device_type__is_console_server',
@@ -192,158 +484,329 @@ class DeviceFilter(django_filters.FilterSet):
name='device_type__is_network_device',
label='Is a network device',
)
mac_address = django_filters.CharFilter(
method='_mac_address',
label='MAC address',
)
has_primary_ip = django_filters.BooleanFilter(
method='_has_primary_ip',
label='Has a primary IP',
)
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
name='virtual_chassis',
queryset=VirtualChassis.objects.all(),
label='Virtual chassis (ID)',
)
class Meta:
model = Device
fields = ['q', 'name', 'site_id', 'site', 'rack_id', 'role_id', 'role', 'device_type_id', 'manufacturer_id',
'manufacturer', 'model', 'platform_id', 'platform', 'status', 'is_console_server', 'is_pdu',
'is_network_device']
fields = ['serial', 'position']
def search(self, queryset, value):
value = value.strip()
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(serial__icontains=value) |
Q(modules__serial__icontains=value)
Q(serial__icontains=value.strip()) |
Q(inventory_items__serial__icontains=value.strip()) |
Q(asset_tag=value.strip()) |
Q(comments__icontains=value)
).distinct()
def _mac_address(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
try:
mac = EUI(value.strip())
return queryset.filter(interfaces__mac_address=mac).distinct()
except AddrFormatError:
return queryset.none()
class ConsolePortFilter(django_filters.FilterSet):
device_id = django_filters.ModelMultipleChoiceFilter(
name='device',
def _has_primary_ip(self, queryset, name, value):
if value:
return queryset.filter(
Q(primary_ip4__isnull=False) |
Q(primary_ip6__isnull=False)
)
else:
return queryset.exclude(
Q(primary_ip4__isnull=False) |
Q(primary_ip6__isnull=False)
)
class DeviceComponentFilterSet(django_filters.FilterSet):
device_id = django_filters.ModelChoiceFilter(
queryset=Device.objects.all(),
label='Device (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
name='device',
device = django_filters.ModelChoiceFilter(
queryset=Device.objects.all(),
to_field_name='name',
label='Device (name)',
)
class ConsolePortFilter(DeviceComponentFilterSet):
class Meta:
model = ConsolePort
fields = ['device_id', 'device', 'name']
fields = ['name']
class ConsoleServerPortFilter(django_filters.FilterSet):
device_id = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
label='Device (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
to_field_name='name',
label='Device (name)',
)
class ConsoleServerPortFilter(DeviceComponentFilterSet):
class Meta:
model = ConsoleServerPort
fields = ['device_id', 'device', 'name']
fields = ['name']
class PowerPortFilter(django_filters.FilterSet):
device_id = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
label='Device (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
to_field_name='name',
label='Device (name)',
)
class PowerPortFilter(DeviceComponentFilterSet):
class Meta:
model = PowerPort
fields = ['device_id', 'device', 'name']
fields = ['name']
class PowerOutletFilter(django_filters.FilterSet):
device_id = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
label='Device (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
to_field_name='name',
label='Device (name)',
)
class PowerOutletFilter(DeviceComponentFilterSet):
class Meta:
model = PowerOutlet
fields = ['device_id', 'device', 'name']
fields = ['name']
class InterfaceFilter(django_filters.FilterSet):
device_id = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
"""
Not using DeviceComponentFilterSet for Interfaces because we need to glean the ordering logic from the parent
Device's DeviceType.
"""
device = django_filters.CharFilter(
method='filter_device',
name='name',
label='Device',
)
device_id = django_filters.NumberFilter(
method='filter_device',
name='pk',
label='Device (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
name='device',
queryset=Device.objects.all(),
to_field_name='name',
label='Device (name)',
type = django_filters.CharFilter(
method='filter_type',
label='Interface type',
)
lag_id = django_filters.ModelMultipleChoiceFilter(
name='lag',
queryset=Interface.objects.all(),
label='LAG interface (ID)',
)
mac_address = django_filters.CharFilter(
method='_mac_address',
label='MAC address',
)
class Meta:
model = Interface
fields = ['device_id', 'device', 'name']
fields = ['name', 'form_factor', 'enabled', 'mtu', 'mgmt_only']
def filter_device(self, queryset, name, value):
try:
device = Device.objects.select_related('device_type').get(**{name: value})
vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')]
ordering = device.device_type.interface_ordering
return queryset.filter(pk__in=vc_interface_ids).order_naturally(ordering)
except Device.DoesNotExist:
return queryset.none()
def filter_type(self, queryset, name, value):
value = value.strip().lower()
return {
'physical': queryset.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES),
'virtual': queryset.filter(form_factor__in=VIRTUAL_IFACE_TYPES),
'wireless': queryset.filter(form_factor__in=WIRELESS_IFACE_TYPES),
'lag': queryset.filter(form_factor=IFACE_FF_LAG),
}.get(value, queryset.none())
def _mac_address(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
try:
mac = EUI(value.strip())
return queryset.filter(mac_address=mac)
except AddrFormatError:
return queryset.none()
class DeviceBayFilter(DeviceComponentFilterSet):
class Meta:
model = DeviceBay
fields = ['name']
class InventoryItemFilter(DeviceComponentFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
asset_tag = NullableCharFieldFilter()
class Meta:
model = InventoryItem
fields = ['name', 'part_id', 'serial', 'asset_tag', 'discovered']
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(part_id__icontains=value) |
Q(serial__iexact=value) |
Q(asset_tag__iexact=value) |
Q(description__icontains=value)
)
return queryset.filter(qs_filter)
class VirtualChassisFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='master__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='master__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site name (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
name='master__tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='master__tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
class Meta:
model = VirtualChassis
fields = ['domain']
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(master__name__icontains=value) |
Q(domain__icontains=value)
)
return queryset.filter(qs_filter)
class ConsoleConnectionFilter(django_filters.FilterSet):
site = django_filters.MethodFilter(
action='filter_site',
site = django_filters.CharFilter(
method='filter_site',
label='Site (slug)',
)
device = django_filters.CharFilter(
method='filter_device',
label='Device',
)
class Meta:
model = ConsoleServerPort
model = ConsolePort
fields = ['name', 'connection_status']
def filter_site(self, queryset, value):
value = value.strip()
if not value:
def filter_site(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(cs_port__device__rack__site__slug=value)
return queryset.filter(cs_port__device__site__slug=value)
def filter_device(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(device__name__icontains=value) |
Q(cs_port__device__name__icontains=value)
)
class PowerConnectionFilter(django_filters.FilterSet):
site = django_filters.MethodFilter(
action='filter_site',
site = django_filters.CharFilter(
method='filter_site',
label='Site (slug)',
)
device = django_filters.CharFilter(
method='filter_device',
label='Device',
)
class Meta:
model = PowerOutlet
model = PowerPort
fields = ['name', 'connection_status']
def filter_site(self, queryset, value):
value = value.strip()
if not value:
def filter_site(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(power_outlet__device__rack__site__slug=value)
return queryset.filter(power_outlet__device__site__slug=value)
def filter_device(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(device__name__icontains=value) |
Q(power_outlet__device__name__icontains=value)
)
class InterfaceConnectionFilter(django_filters.FilterSet):
site = django_filters.MethodFilter(
action='filter_site',
site = django_filters.CharFilter(
method='filter_site',
label='Site (slug)',
)
device = django_filters.CharFilter(
method='filter_device',
label='Device',
)
class Meta:
model = InterfaceConnection
fields = ['connection_status']
def filter_site(self, queryset, value):
value = value.strip()
if not value:
def filter_site(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(interface_a__device__rack__site__slug=value) |
Q(interface_b__device__rack__site__slug=value)
Q(interface_a__device__site__slug=value) |
Q(interface_b__device__site__slug=value)
)
def filter_device(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(interface_a__device__name__icontains=value) |
Q(interface_b__device__name__icontains=value)
)

View File

@@ -1915,6 +1915,7 @@
"platform": 1,
"name": "test1-edge1",
"serial": "5555555555",
"site": 1,
"rack": 1,
"position": 1,
"face": 0,
@@ -1935,6 +1936,7 @@
"platform": 1,
"name": "test1-core1",
"serial": "",
"site": 1,
"rack": 1,
"position": 17,
"face": 0,
@@ -1955,6 +1957,7 @@
"platform": 1,
"name": "test1-spine1",
"serial": "",
"site": 1,
"rack": 1,
"position": 33,
"face": 0,
@@ -1975,6 +1978,7 @@
"platform": 1,
"name": "test1-leaf1",
"serial": "",
"site": 1,
"rack": 1,
"position": 34,
"face": 0,
@@ -1995,6 +1999,7 @@
"platform": 1,
"name": "test1-leaf2",
"serial": "9823478293748",
"site": 1,
"rack": 2,
"position": 34,
"face": 0,
@@ -2015,6 +2020,7 @@
"platform": 1,
"name": "test1-spine2",
"serial": "45649818158",
"site": 1,
"rack": 2,
"position": 33,
"face": 0,
@@ -2035,6 +2041,7 @@
"platform": 1,
"name": "test1-edge2",
"serial": "7567356345",
"site": 1,
"rack": 2,
"position": 1,
"face": 0,
@@ -2055,6 +2062,7 @@
"platform": 1,
"name": "test1-core2",
"serial": "67856734534",
"site": 1,
"rack": 2,
"position": 17,
"face": 0,
@@ -2075,6 +2083,7 @@
"platform": 2,
"name": "test1-oob1",
"serial": "98273942938",
"site": 1,
"rack": 1,
"position": 42,
"face": 0,
@@ -2095,6 +2104,7 @@
"platform": null,
"name": "test1-pdu1",
"serial": "",
"site": 1,
"rack": 1,
"position": null,
"face": null,
@@ -2115,6 +2125,7 @@
"platform": null,
"name": "test1-pdu2",
"serial": "",
"site": 1,
"rack": 2,
"position": null,
"face": null,

View File

@@ -0,0 +1,201 @@
[
{
"model": "dcim.devicerole",
"pk": 1,
"fields": {
"name": "Console Server",
"slug": "console-server",
"color": "009688"
}
},
{
"model": "dcim.devicerole",
"pk": 2,
"fields": {
"name": "Core Switch",
"slug": "core-switch",
"color": "2196f3"
}
},
{
"model": "dcim.devicerole",
"pk": 3,
"fields": {
"name": "Distribution Switch",
"slug": "distribution-switch",
"color": "2196f3"
}
},
{
"model": "dcim.devicerole",
"pk": 4,
"fields": {
"name": "Access Switch",
"slug": "access-switch",
"color": "2196f3"
}
},
{
"model": "dcim.devicerole",
"pk": 5,
"fields": {
"name": "Management Switch",
"slug": "management-switch",
"color": "ff9800"
}
},
{
"model": "dcim.devicerole",
"pk": 6,
"fields": {
"name": "Firewall",
"slug": "firewall",
"color": "f44336"
}
},
{
"model": "dcim.devicerole",
"pk": 7,
"fields": {
"name": "Router",
"slug": "router",
"color": "9c27b0"
}
},
{
"model": "dcim.devicerole",
"pk": 8,
"fields": {
"name": "Server",
"slug": "server",
"color": "9e9e9e"
}
},
{
"model": "dcim.devicerole",
"pk": 9,
"fields": {
"name": "PDU",
"slug": "pdu",
"color": "607d8b"
}
},
{
"model": "dcim.manufacturer",
"pk": 1,
"fields": {
"name": "APC",
"slug": "apc"
}
},
{
"model": "dcim.manufacturer",
"pk": 2,
"fields": {
"name": "Cisco",
"slug": "cisco"
}
},
{
"model": "dcim.manufacturer",
"pk": 3,
"fields": {
"name": "Dell",
"slug": "dell"
}
},
{
"model": "dcim.manufacturer",
"pk": 4,
"fields": {
"name": "HP",
"slug": "hp"
}
},
{
"model": "dcim.manufacturer",
"pk": 5,
"fields": {
"name": "Juniper",
"slug": "juniper"
}
},
{
"model": "dcim.manufacturer",
"pk": 6,
"fields": {
"name": "Arista",
"slug": "arista"
}
},
{
"model": "dcim.manufacturer",
"pk": 7,
"fields": {
"name": "Opengear",
"slug": "opengear"
}
},
{
"model": "dcim.manufacturer",
"pk": 8,
"fields": {
"name": "Super Micro",
"slug": "super-micro"
}
},
{
"model": "dcim.platform",
"pk": 1,
"fields": {
"name": "Cisco IOS",
"slug": "cisco-ios",
"rpc_client": "cisco-ios"
}
},
{
"model": "dcim.platform",
"pk": 2,
"fields": {
"name": "Cisco NX-OS",
"slug": "cisco-nx-os",
"rpc_client": ""
}
},
{
"model": "dcim.platform",
"pk": 3,
"fields": {
"name": "Juniper Junos",
"slug": "juniper-junos",
"rpc_client": "juniper-junos"
}
},
{
"model": "dcim.platform",
"pk": 4,
"fields": {
"name": "Arista EOS",
"slug": "arista-eos",
"rpc_client": ""
}
},
{
"model": "dcim.platform",
"pk": 5,
"fields": {
"name": "Linux",
"slug": "linux",
"rpc_client": ""
}
},
{
"model": "dcim.platform",
"pk": 6,
"fields": {
"name": "Opengear",
"slug": "opengear",
"rpc_client": "opengear"
}
}
]

View File

@@ -1,7 +1,8 @@
from netaddr import EUI, AddrFormatError
from __future__ import unicode_literals
from django import forms
from django.core.exceptions import ValidationError
from netaddr import EUI, AddrFormatError
#

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-07-26 21:59
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0001_initial'),
('dcim', '0011_devicetype_part_number'),
]
operations = [
migrations.AddField(
model_name='device',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.Tenant'),
),
migrations.AddField(
model_name='rack',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.Tenant'),
),
migrations.AddField(
model_name='site',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.Tenant'),
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-06 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0012_site_rack_device_add_tenant'),
]
operations = [
migrations.AlterField(
model_name='interface',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200),
),
migrations.AlterField(
model_name='interfacetemplate',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200),
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-08 21:11
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0013_add_interface_form_factors'),
]
operations = [
migrations.AddField(
model_name='rack',
name='type',
field=models.PositiveSmallIntegerField(blank=True, choices=[(100, b'2-post frame'), (200, b'4-post frame'), (300, b'4-post cabinet'), (1000, b'Wall-mounted frame'), (1100, b'Wall-mounted cabinet')], null=True, verbose_name=b'Type'),
),
migrations.AddField(
model_name='rack',
name='width',
field=models.PositiveSmallIntegerField(choices=[(19, b'19 inches'), (23, b'23 inches')], default=19, help_text=b'Rail-to-rail width', verbose_name=b'Width'),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-09 21:18
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0014_rack_add_type_width'),
]
operations = [
migrations.AlterField(
model_name='rack',
name='u_height',
field=models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name=b'Height (U)'),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-10 13:45
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0015_rack_add_u_height_validator'),
]
operations = [
migrations.AddField(
model_name='module',
name='manufacturer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='modules', to='dcim.Manufacturer'),
),
]

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-10 14:58
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0016_module_add_manufacturer'),
]
operations = [
migrations.CreateModel(
name='RackRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True)),
('slug', models.SlugField(unique=True)),
('color', models.CharField(choices=[[b'teal', b'Teal'], [b'green', b'Green'], [b'blue', b'Blue'], [b'purple', b'Purple'], [b'yellow', b'Yellow'], [b'orange', b'Orange'], [b'red', b'Red'], [b'light_gray', b'Light Gray'], [b'medium_gray', b'Medium Gray'], [b'dark_gray', b'Dark Gray']], max_length=30)),
],
options={
'ordering': ['name'],
},
),
migrations.AddField(
model_name='rack',
name='role',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.RackRole'),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-08-11 15:42
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields
class Migration(migrations.Migration):
dependencies = [
('dcim', '0017_rack_add_role'),
]
operations = [
migrations.AddField(
model_name='device',
name='asset_tag',
field=utilities.fields.NullableCharField(blank=True, help_text=b'A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name=b'Asset tag'),
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-09-13 15:20
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0018_device_add_asset_tag'),
]
operations = [
migrations.AlterField(
model_name='interface',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
migrations.AlterField(
model_name='interfacetemplate',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-10-28 15:01
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0019_new_iface_form_factors'),
]
operations = [
migrations.AddField(
model_name='rack',
name='desc_units',
field=models.BooleanField(default=False, help_text=b'Units are numbered top-to-bottom', verbose_name=b'Descending units'),
),
]

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-10-31 18:47
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0020_rack_desc_units'),
]
operations = [
migrations.AlterField(
model_name='device',
name='position',
field=models.PositiveSmallIntegerField(blank=True, help_text=b'The lowest-numbered unit occupied by the device', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name=b'Position (U)'),
),
migrations.AlterField(
model_name='interface',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
migrations.AlterField(
model_name='interfacetemplate',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
]

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-12-06 16:35
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields
COLOR_CONVERSION = {
'teal': '009688',
'green': '4caf50',
'blue': '2196f3',
'purple': '9c27b0',
'yellow': 'ffeb3b',
'orange': 'ff9800',
'red': 'f44336',
'light_gray': 'c0c0c0',
'medium_gray': '9e9e9e',
'dark_gray': '607d8b',
}
def color_names_to_rgb(apps, schema_editor):
RackRole = apps.get_model('dcim', 'RackRole')
DeviceRole = apps.get_model('dcim', 'DeviceRole')
for color_name, color_rgb in COLOR_CONVERSION.items():
RackRole.objects.filter(color=color_name).update(color=color_rgb)
DeviceRole.objects.filter(color=color_name).update(color=color_rgb)
def color_rgb_to_name(apps, schema_editor):
RackRole = apps.get_model('dcim', 'RackRole')
DeviceRole = apps.get_model('dcim', 'DeviceRole')
for color_name, color_rgb in COLOR_CONVERSION.items():
RackRole.objects.filter(color=color_rgb).update(color=color_name)
DeviceRole.objects.filter(color=color_rgb).update(color=color_name)
class Migration(migrations.Migration):
dependencies = [
('dcim', '0021_add_ff_flexstack'),
]
operations = [
migrations.RunPython(color_names_to_rgb, color_rgb_to_name),
migrations.AlterField(
model_name='devicerole',
name='color',
field=utilities.fields.ColorField(max_length=6),
),
migrations.AlterField(
model_name='rackrole',
name='color',
field=utilities.fields.ColorField(max_length=6),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-12-16 16:08
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0022_color_names_to_rgb'),
]
operations = [
migrations.AddField(
model_name='devicetype',
name='comments',
field=models.TextField(blank=True),
),
]

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-29 16:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0023_devicetype_comments'),
]
operations = [
migrations.AddField(
model_name='site',
name='contact_email',
field=models.EmailField(blank=True, max_length=254, verbose_name=b'Contact E-mail'),
),
migrations.AddField(
model_name='site',
name='contact_name',
field=models.CharField(blank=True, max_length=50),
),
migrations.AddField(
model_name='site',
name='contact_phone',
field=models.CharField(blank=True, max_length=20),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 16:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0024_site_add_contact_fields'),
]
operations = [
migrations.AddField(
model_name='devicetype',
name='interface_ordering',
field=models.PositiveSmallIntegerField(choices=[[1, b'Slot/position'], [2, b'Name (alphabetically)']], default=1),
),
]

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 18:43
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('dcim', '0025_devicetype_add_interface_ordering'),
]
operations = [
migrations.CreateModel(
name='RackReservation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('units', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(), size=None)),
('created', models.DateTimeField(auto_now_add=True)),
('description', models.CharField(max_length=100)),
('rack', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.Rack')),
('user', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['created'],
},
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 21:21
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0026_add_rack_reservations'),
]
operations = [
migrations.AddField(
model_name='device',
name='site',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Site'),
),
]

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 21:23
from __future__ import unicode_literals
from django.db import migrations
def copy_site_from_rack(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for device in Device.objects.all():
device.site = device.rack.site
device.save()
class Migration(migrations.Migration):
dependencies = [
('dcim', '0027_device_add_site'),
]
operations = [
migrations.RunPython(copy_site_from_rack),
]

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 21:25
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0028_device_copy_rack_to_site'),
]
operations = [
migrations.AlterField(
model_name='device',
name='rack',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Rack'),
),
migrations.AlterField(
model_name='device',
name='site',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Site'),
),
]

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-27 19:55
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0029_allow_rackless_devices'),
]
operations = [
migrations.AddField(
model_name='interface',
name='lag',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.Interface', verbose_name=b'Parent LAG'),
),
migrations.AlterField(
model_name='interface',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
migrations.AlterField(
model_name='interfacetemplate',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
]

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-28 17:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('dcim', '0030_interface_add_lag'),
]
operations = [
migrations.CreateModel(
name='Region',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True)),
('slug', models.SlugField(unique=True)),
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(db_index=True, editable=False)),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.Region')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='site',
name='region',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.Region'),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-03-02 15:09
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields
class Migration(migrations.Migration):
dependencies = [
('dcim', '0031_regions'),
]
operations = [
migrations.AlterField(
model_name='device',
name='name',
field=utilities.fields.NullableCharField(blank=True, max_length=64, null=True, unique=True),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-03-17 18:39
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0032_device_increase_name_length'),
]
operations = [
migrations.AlterField(
model_name='rackreservation',
name='rack',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.Rack'),
),
]

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-03-21 14:55
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0033_rackreservation_rack_editable'),
]
operations = [
migrations.RenameModel(
old_name='Module',
new_name='InventoryItem',
),
migrations.AlterField(
model_name='inventoryitem',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to='dcim.Device'),
),
migrations.AlterField(
model_name='inventoryitem',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.InventoryItem'),
),
migrations.AlterField(
model_name='inventoryitem',
name='manufacturer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_items', to='dcim.Manufacturer'),
),
]

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-05-08 15:57
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0034_rename_module_to_inventoryitem'),
]
# We convert the BooleanField to an IntegerField first as PostgreSQL does not provide a direct cast for boolean to
# smallint (attempting to convert directly yields the error "cannot cast type boolean to smallint").
operations = [
migrations.AlterField(
model_name='device',
name='status',
field=models.PositiveIntegerField(choices=[[1, b'Active'], [0, b'Offline'], [2, b'Planned'], [3, b'Staged'], [4, b'Failed'], [5, b'Inventory']], default=1, verbose_name=b'Status'),
),
migrations.AlterField(
model_name='device',
name='status',
field=models.PositiveSmallIntegerField(choices=[[1, b'Active'], [0, b'Offline'], [2, b'Planned'], [3, b'Staged'], [4, b'Failed'], [5, b'Inventory']], default=1, verbose_name=b'Status'),
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-09 16:00
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0035_device_expand_status_choices'),
]
operations = [
migrations.AlterField(
model_name='interface',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus'], [5200, b'Juniper VCP']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
migrations.AlterField(
model_name='interfacetemplate',
name='form_factor',
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus'], [5200, b'Juniper VCP']]], [b'Other', [[32767, b'Other']]]], default=1200),
),
]

Some files were not shown because too many files have changed in this diff Show More