Procházet zdrojové kódy

Merge pull request #2772 from digitalocean/select2-ui

Select2 UI
John Anderson před 7 roky
rodič
revize
1abbaf99dc
82 změnil soubory, kde provedl 16171 přidání a 2639 odebrání
  1. 2 0
      .gitignore
  2. 1910 1903
      CHANGELOG.md
  3. 62 28
      netbox/circuits/forms.py
  4. 11 0
      netbox/dcim/filters.py
  5. 320 157
      netbox/dcim/forms.py
  6. 256 129
      netbox/ipam/forms.py
  7. 111 0
      netbox/project-static/css/base.css
  8. 194 108
      netbox/project-static/js/forms.js
  9. 21 0
      netbox/project-static/select2-4.0.5/LICENSE.md
  10. 123 0
      netbox/project-static/select2-4.0.5/README.md
  11. 484 0
      netbox/project-static/select2-4.0.5/css/select2.css
  12. 0 0
      netbox/project-static/select2-4.0.5/css/select2.min.css
  13. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/af.js
  14. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ar.js
  15. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/az.js
  16. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/bg.js
  17. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/bs.js
  18. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ca.js
  19. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/cs.js
  20. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/da.js
  21. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/de.js
  22. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/dsb.js
  23. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/el.js
  24. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/en.js
  25. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/es.js
  26. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/et.js
  27. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/eu.js
  28. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/fa.js
  29. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/fi.js
  30. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/fr.js
  31. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/gl.js
  32. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/he.js
  33. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/hi.js
  34. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/hr.js
  35. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/hsb.js
  36. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/hu.js
  37. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/hy.js
  38. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/id.js
  39. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/is.js
  40. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/it.js
  41. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ja.js
  42. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/km.js
  43. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ko.js
  44. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/lt.js
  45. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/lv.js
  46. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/mk.js
  47. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ms.js
  48. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/nb.js
  49. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/nl.js
  50. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/pl.js
  51. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ps.js
  52. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/pt-BR.js
  53. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/pt.js
  54. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ro.js
  55. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/ru.js
  56. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/sk.js
  57. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/sl.js
  58. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/sr-Cyrl.js
  59. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/sr.js
  60. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/sv.js
  61. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/th.js
  62. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/tr.js
  63. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/uk.js
  64. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/vi.js
  65. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/zh-CN.js
  66. 3 0
      netbox/project-static/select2-4.0.5/js/i18n/zh-TW.js
  67. 6457 0
      netbox/project-static/select2-4.0.5/js/select2.full.js
  68. 0 0
      netbox/project-static/select2-4.0.5/js/select2.full.min.js
  69. 5746 0
      netbox/project-static/select2-4.0.5/js/select2.js
  70. 0 0
      netbox/project-static/select2-4.0.5/js/select2.min.js
  71. 23 7
      netbox/secrets/forms.py
  72. 2 0
      netbox/templates/_base.html
  73. 2 2
      netbox/templates/dcim/cable_connect.html
  74. 0 85
      netbox/templates/dcim/device_list.html
  75. 0 29
      netbox/templates/dcim/inc/filter_rack_group.html
  76. 0 5
      netbox/templates/dcim/rack_list.html
  77. 1 1
      netbox/templates/ipam/ipaddress_edit.html
  78. 0 29
      netbox/templates/virtualization/virtualmachine_list.html
  79. 24 9
      netbox/tenancy/forms.py
  80. 102 83
      netbox/utilities/forms.py
  81. 1 1
      netbox/utilities/templates/widgets/colorselect_option.html
  82. 157 63
      netbox/virtualization/forms.py

+ 2 - 0
.gitignore

@@ -10,3 +10,5 @@
 fabfile.py
 *.swp
 gunicorn_config.py
+.DS_Store
+.vscode

+ 1910 - 1903
CHANGELOG.md

@@ -1,1903 +1,1910 @@
-v2.5.4 (FUTURE)
-
-## Bug Fixes
-
-* [#2779](https://github.com/digitalocean/netbox/issues/2779) - Include "none" option when filter IP addresses by role
-* [#2783](https://github.com/digitalocean/netbox/issues/2783) - Fix AttributeError exception when attempting to delete region(s)
-
----
-
-v2.5.3 (2019-01-11)
-
-## Enhancements
-
-* [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length
-* [#1870](https://github.com/digitalocean/netbox/issues/1870) - Add per-page toggle to object lists
-* [#1871](https://github.com/digitalocean/netbox/issues/1871) - Enable filtering sites by parent region
-* [#1983](https://github.com/digitalocean/netbox/issues/1983) - Enable regular expressions when bulk renaming device components
-* [#2682](https://github.com/digitalocean/netbox/issues/2682) - Add DAC and AOC cable types
-* [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors
-* [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search
-
-## Bug Fixes
-
-* [#2742](https://github.com/digitalocean/netbox/issues/2742) - Preserve cluster assignment when editing a device
-* [#2757](https://github.com/digitalocean/netbox/issues/2757) - Always treat first/last IPs within a /31 or /127 as usable
-* [#2762](https://github.com/digitalocean/netbox/issues/2762) - Add missing DCIM field values to API `_choices` endpoint
-* [#2777](https://github.com/digitalocean/netbox/issues/2777) - Fix cable validation to handle duplicate connections on import
-
-
----
-
-v2.5.2 (2018-12-21)
-
-## Enhancements
-
-* [#2561](https://github.com/digitalocean/netbox/issues/2561) - Add 200G and 400G interface types
-* [#2701](https://github.com/digitalocean/netbox/issues/2701) - Enable filtering of prefixes by exact prefix value
-
-## Bug Fixes
-
-* [#2673](https://github.com/digitalocean/netbox/issues/2673) - Fix exception on LLDP neighbors view for device with a circuit connected
-* [#2691](https://github.com/digitalocean/netbox/issues/2691) - Cable trace should follow circuits
-* [#2698](https://github.com/digitalocean/netbox/issues/2698) - Remove pagination restriction on bulk component creation for devices/VMs
-* [#2704](https://github.com/digitalocean/netbox/issues/2704) - Fix form select widget population on parent with null value
-* [#2707](https://github.com/digitalocean/netbox/issues/2707) - Correct permission evaluation for circuit termination cabling
-* [#2712](https://github.com/digitalocean/netbox/issues/2712) - Preserve list filtering after editing objects in bulk
-* [#2717](https://github.com/digitalocean/netbox/issues/2717) - Fix bulk deletion of tags
-* [#2721](https://github.com/digitalocean/netbox/issues/2721) - Detect loops when tracing front/rear ports
-* [#2723](https://github.com/digitalocean/netbox/issues/2723) - Correct permission evaluation when bulk deleting tags
-* [#2724](https://github.com/digitalocean/netbox/issues/2724) - Limit rear port choices to current device when editing a front port
-
----
-
-v2.5.1 (2018-12-13)
-
-## Enhancements
-
-* [#2655](https://github.com/digitalocean/netbox/issues/2655) - Add 128GFC Fibrechannel interface type
-* [#2674](https://github.com/digitalocean/netbox/issues/2674) - Enable filtering changelog by object type under web UI
-
-## Bug Fixes
-
-* [#2662](https://github.com/digitalocean/netbox/issues/2662) - Fix ImproperlyConfigured exception when rendering API docs
-* [#2663](https://github.com/digitalocean/netbox/issues/2663) - Prevent duplicate interfaces from appearing under VLAN members view
-* [#2666](https://github.com/digitalocean/netbox/issues/2666) - Correct display of length unit in cables list
-* [#2676](https://github.com/digitalocean/netbox/issues/2676) - Fix exception when passing dictionary value to a ChoiceField
-* [#2678](https://github.com/digitalocean/netbox/issues/2678) - Fix error when viewing webhook in admin UI without write permission
-* [#2680](https://github.com/digitalocean/netbox/issues/2680) - Disallow POST requests to `/dcim/interface-connections/` API endpoint
-* [#2683](https://github.com/digitalocean/netbox/issues/2683) - Fix exception when connecting a cable to a RearPort with no corresponding FrontPort
-* [#2684](https://github.com/digitalocean/netbox/issues/2684) - Fix custom field filtering
-* [#2687](https://github.com/digitalocean/netbox/issues/2687) - Correct naming of before/after filters for changelog entries
-
----
-
-v2.5.0 (2018-12-10)
-
-## Notes
-
-### Python 3 Required
-
-As promised, Python 2 support has been completed removed. Python 3.5 or higher is now required to run NetBox. Please see [our Python 3 migration guide](https://netbox.readthedocs.io/en/stable/installation/migrating-to-python3/) for assistance with upgrading.
-
-### Removed Deprecated User Activity Log
-
-The UserAction model, which was deprecated by the new change logging feature in NetBox v2.4, has been removed. If you need to archive legacy user activity, do so prior to upgrading to NetBox v2.5, as the database migration will remove all data associated with this model.
-
-### View Permissions in Django 2.1
-
-Django 2.1 introduces view permissions for object types (not to be confused with object-level permissions). Implementation of [#323](https://github.com/digitalocean/netbox/issues/323) is planned for NetBox v2.6. Users are encourage to begin assigning view permissions as desired in preparation for their eventual enforcement.
-
-### upgrade.sh No Longer Invokes sudo
-
-The `upgrade.sh` script has been tweaked so that it no longer invokes `sudo` internally. This was done to ensure compatibility when running NetBox inside a Python virtual environment. If you need elevated permissions when upgrading NetBox, call the upgrade script with `sudo upgrade.sh`.
-
-## New Features
-
-### Patch Panels and Cables ([#20](https://github.com/digitalocean/netbox/issues/20))
-
-NetBox now supports modeling physical cables for console, power, and interface connections. The new pass-through port component type has also been introduced to model patch panels and similar devices.
-
-## Enhancements
-
-* [#450](https://github.com/digitalocean/netbox/issues/450) - Added `outer_width` and `outer_depth` fields to rack model
-* [#867](https://github.com/digitalocean/netbox/issues/867) - Added `description` field to circuit terminations
-* [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added an `asset_tag` field for racks
-* [#1931](https://github.com/digitalocean/netbox/issues/1931) - Added a count of assigned IP addresses to the interface API serializer
-* [#2000](https://github.com/digitalocean/netbox/issues/2000) - Dropped support for Python 2
-* [#2053](https://github.com/digitalocean/netbox/issues/2053) - Introduced the `LOGIN_TIMEOUT` configuration setting
-* [#2057](https://github.com/digitalocean/netbox/issues/2057) - Added description columns to interface connections list
-* [#2104](https://github.com/digitalocean/netbox/issues/2104) - Added a `status` field for racks
-* [#2165](https://github.com/digitalocean/netbox/issues/2165) - Improved natural ordering of Interfaces
-* [#2292](https://github.com/digitalocean/netbox/issues/2292) - Removed the deprecated UserAction model
-* [#2367](https://github.com/digitalocean/netbox/issues/2367) - Removed deprecated RPCClient functionality
-* [#2426](https://github.com/digitalocean/netbox/issues/2426) - Introduced `SESSION_FILE_PATH` configuration setting for authentication without write access to database
-* [#2594](https://github.com/digitalocean/netbox/issues/2594) - `upgrade.sh` no longer invokes sudo
-
-## Changes From v2.5-beta2
-
-* [#2474](https://github.com/digitalocean/netbox/issues/2474) - Add `cabled` and `connection_status` filters for device components
-* [#2616](https://github.com/digitalocean/netbox/issues/2616) - Convert Rack `outer_unit` and Cable `length_unit` to integer-based choice fields
-* [#2622](https://github.com/digitalocean/netbox/issues/2622) - Enable filtering cables by multiple types/colors
-* [#2624](https://github.com/digitalocean/netbox/issues/2624) - Delete associated content type and permissions when removing InterfaceConnection model
-* [#2626](https://github.com/digitalocean/netbox/issues/2626) - Remove extraneous permissions generated from proxy models
-* [#2632](https://github.com/digitalocean/netbox/issues/2632) - Change representation of null values from `0` to `null`
-* [#2639](https://github.com/digitalocean/netbox/issues/2639) - Fix preservation of length/dimensions unit for racks and cables
-* [#2648](https://github.com/digitalocean/netbox/issues/2648) - Include the `connection_status` field in nested represenations of connectable device components
-* [#2649](https://github.com/digitalocean/netbox/issues/2649) - Add `connected_endpoint_type` to connectable device component API representations
-
-## API Changes
-
-* The `/extras/recent-activity/` endpoint (replaced by change logging in v2.4) has been removed
-* The `rpc_client` field has been removed from dcim.Platform (see #2367)
-* Introduced a new API endpoint for cables at `/dcim/cables/`
-* New endpoints for front and rear pass-through ports (and their templates) in parallel with existing device components
-* The fields `interface_connection` on Interface and `interface` on CircuitTermination have been replaced with `connected_endpoint` and `connection_status`
-* A new `cable` field has been added to console, power, and interface components and to circuit terminations
-* New fields for dcim.Rack: `status`, `asset_tag`, `outer_width`, `outer_depth`, `outer_unit`
-* The following boolean filters on dcim.Device and dcim.DeviceType have been renamed:
-    * `is_console_server`: `console_server_ports`
-    * `is_pdu`: `power_outlets`
-    * `is_network_device`: `interfaces`
-* The following new boolean filters have been introduced for dcim.Device and dcim.DeviceType:
-    * `console_ports`
-    * `power_ports`
-    * `pass_through_ports`
-* The field `interface_ordering` has been removed from the DeviceType serializer
-* Added a `description` field to the CircuitTermination serializer
-* Added `ipaddress_count` to InterfaceSerializer to show the count of assigned IP addresses for each interface
-* The `available-prefixes` and `available-ips` IPAM endpoints now return an HTTP 204 response instead of HTTP 400 when no new objects can be created
-* Filtering on null values now uses the string `null` instead of zero
-
----
-
-v2.4.9 (2018-12-07)
-
-## Enhancements
-
-* [#2089](https://github.com/digitalocean/netbox/issues/2089) - Add SONET interface form factors
-* [#2495](https://github.com/digitalocean/netbox/issues/2495) - Enable deep-merging of config context data
-* [#2597](https://github.com/digitalocean/netbox/issues/2597) - Add FibreChannel SFP28 (32GFC) interface form factor
-
-## Bug Fixes
-
-* [#2400](https://github.com/digitalocean/netbox/issues/2400) - Correct representation of nested object assignment in API docs
-* [#2576](https://github.com/digitalocean/netbox/issues/2576) - Correct type for count_* fields in site API representation
-* [#2606](https://github.com/digitalocean/netbox/issues/2606) - Fixed filtering for interfaces with a virtual form factor
-* [#2611](https://github.com/digitalocean/netbox/issues/2611) - Fix error handling when assigning a clustered device to a different site
-* [#2613](https://github.com/digitalocean/netbox/issues/2613) - Decrease live search minimum characters to three
-* [#2615](https://github.com/digitalocean/netbox/issues/2615) - Tweak live search widget to use brief format for API requests
-* [#2623](https://github.com/digitalocean/netbox/issues/2623) - Removed the need to pass the model class to the rqworker process for webhooks
-* [#2634](https://github.com/digitalocean/netbox/issues/2634) - Enforce consistent representation of unnamed devices in rack view
-
----
-
-v2.4.8 (2018-11-20)
-
-## Enhancements
-
-* [#2490](https://github.com/digitalocean/netbox/issues/2490) - Added bulk editing for config contexts
-* [#2557](https://github.com/digitalocean/netbox/issues/2557) - Added object view for tags
-
-## Bug Fixes
-
-* [#2473](https://github.com/digitalocean/netbox/issues/2473) - Fix encoding of long (>127 character) secrets
-* [#2558](https://github.com/digitalocean/netbox/issues/2558) - Filter on all tags when multiple are passed
-* [#2565](https://github.com/digitalocean/netbox/issues/2565) - Improved rendering of Markdown tables
-* [#2575](https://github.com/digitalocean/netbox/issues/2575) - Correct model specified for rack roles table
-* [#2588](https://github.com/digitalocean/netbox/issues/2588) - Catch all exceptions from failed NAPALM API Calls
-* [#2589](https://github.com/digitalocean/netbox/issues/2589) - Virtual machine API serializer should require cluster assignment
-
----
-
-v2.4.7 (2018-11-06)
-
-## Enhancements
-
-* [#2388](https://github.com/digitalocean/netbox/issues/2388) - Enable filtering of devices/VMs by region
-* [#2427](https://github.com/digitalocean/netbox/issues/2427) - Allow filtering of interfaces by assigned VLAN or VLAN ID
-* [#2512](https://github.com/digitalocean/netbox/issues/2512) - Add device field to inventory item filter form
-
-## Bug Fixes
-
-* [#2502](https://github.com/digitalocean/netbox/issues/2502) - Allow duplicate VIPs inside a uniqueness-enforced VRF
-* [#2514](https://github.com/digitalocean/netbox/issues/2514) - Prevent new connections to already connected interfaces
-* [#2515](https://github.com/digitalocean/netbox/issues/2515) - Only use django-rq admin tmeplate if webhooks are enabled
-* [#2528](https://github.com/digitalocean/netbox/issues/2528) - Enable creating circuit terminations with interface assignment via API
-* [#2549](https://github.com/digitalocean/netbox/issues/2549) - Changed naming of `peer_device` and `peer_interface` on API /dcim/connected-device/ endpoint to use underscores
-
----
-
-v2.4.6 (2018-10-05)
-
-## Enhancements
-
-* [#2479](https://github.com/digitalocean/netbox/issues/2479) - Add user permissions for creating/modifying API tokens
-* [#2487](https://github.com/digitalocean/netbox/issues/2487) - Return abbreviated API output when passed `?brief=1`
-
-## Bug Fixes
-
-* [#2393](https://github.com/digitalocean/netbox/issues/2393) - Fix Unicode support for CSV import under Python 2
-* [#2483](https://github.com/digitalocean/netbox/issues/2483) - Set max item count of API-populated form fields to MAX_PAGE_SIZE
-* [#2484](https://github.com/digitalocean/netbox/issues/2484) - Local config context not available on the Virtual Machine Edit Form
-* [#2485](https://github.com/digitalocean/netbox/issues/2485) - Fix cancel button when assigning a service to a device/VM
-* [#2491](https://github.com/digitalocean/netbox/issues/2491) - Fix exception when importing devices with invalid device type
-* [#2492](https://github.com/digitalocean/netbox/issues/2492) - Sanitize hostname and port values returned through LLDP
-
----
-
-v2.4.5 (2018-10-02)
-
-## Enhancements
-
-* [#2392](https://github.com/digitalocean/netbox/issues/2392) - Implemented local context data for devices and virtual machines
-* [#2402](https://github.com/digitalocean/netbox/issues/2402) - Order and format JSON data in form fields
-* [#2432](https://github.com/digitalocean/netbox/issues/2432) - Link remote interface connections to the Interface view
-* [#2438](https://github.com/digitalocean/netbox/issues/2438) - API optimizations for tagged objects
-
-## Bug Fixes
-
-* [#2406](https://github.com/digitalocean/netbox/issues/2406) - Remove hard-coded limit of 1000 objects from API-populated form fields
-* [#2414](https://github.com/digitalocean/netbox/issues/2414) - Tags field missing from device/VM component creation forms
-* [#2442](https://github.com/digitalocean/netbox/issues/2442) - Nullify "next" link in API when limit=0 is passed
-* [#2443](https://github.com/digitalocean/netbox/issues/2443) - Enforce JSON object format when creating config contexts
-* [#2444](https://github.com/digitalocean/netbox/issues/2444) - Improve validation of interface MAC addresses
-* [#2455](https://github.com/digitalocean/netbox/issues/2455) - Ignore unique address enforcement for IPs with a shared/virtual role
-* [#2470](https://github.com/digitalocean/netbox/issues/2470) - Log the creation of device/VM components as object changes
-
----
-
-v2.4.4 (2018-08-22)
-
-## Enhancements
-
-* [#2168](https://github.com/digitalocean/netbox/issues/2168) - Added Extreme SummitStack interface form factors
-* [#2356](https://github.com/digitalocean/netbox/issues/2356) - Include cluster site as read-only field in VirtualMachine serializer
-* [#2362](https://github.com/digitalocean/netbox/issues/2362) - Implemented custom admin site to properly handle BASE_PATH
-* [#2254](https://github.com/digitalocean/netbox/issues/2254) - Implemented searchability for Rack Groups
-
-## Bug Fixes
-
-* [#2353](https://github.com/digitalocean/netbox/issues/2353) - Handle `DoesNotExist` exception when deleting a device with connected interfaces
-* [#2354](https://github.com/digitalocean/netbox/issues/2354) - Increased maximum MTU for interfaces to 65536 bytes
-* [#2355](https://github.com/digitalocean/netbox/issues/2355) - Added item count to inventory tab on device view
-* [#2368](https://github.com/digitalocean/netbox/issues/2368) - Record change in device changelog when altering cluster assignment
-* [#2369](https://github.com/digitalocean/netbox/issues/2369) - Corrected time zone validation on site API serializer
-* [#2370](https://github.com/digitalocean/netbox/issues/2370) - Redirect to parent device after deleting device bays
-* [#2374](https://github.com/digitalocean/netbox/issues/2374) - Fix toggling display of IP addresses in virtual machine interfaces list
-* [#2378](https://github.com/digitalocean/netbox/issues/2378) - Corrected "edit" link for virtual machine interfaces
-
----
-
-v2.4.3 (2018-08-09)
-
-## Enhancements
-
-* [#2333](https://github.com/digitalocean/netbox/issues/2333) - Added search filters for ConfigContexts
-
-## Bug Fixes
-
-* [#2334](https://github.com/digitalocean/netbox/issues/2334) - TypeError raised when WritableNestedSerializer receives a non-integer value
-* [#2335](https://github.com/digitalocean/netbox/issues/2335) - API requires group field when creating/updating a rack
-* [#2336](https://github.com/digitalocean/netbox/issues/2336) - Bulk deleting power outlets and console server ports from a device redirects to home page
-* [#2337](https://github.com/digitalocean/netbox/issues/2337) - Attempting to create the next available prefix within a parent assigned to a VRF raises an AssertionError
-* [#2340](https://github.com/digitalocean/netbox/issues/2340) - API requires manufacturer field when creating/updating an inventory item
-* [#2342](https://github.com/digitalocean/netbox/issues/2342) - IntegrityError raised when attempting to assign an invalid IP address as the primary for a VM
-* [#2344](https://github.com/digitalocean/netbox/issues/2344) - AttributeError when assigning VLANs to an interface on a device/VM not assigned to a site
-
----
-
-v2.4.2 (2018-08-08)
-
-## Bug Fixes
-
-* [#2318](https://github.com/digitalocean/netbox/issues/2318) - ImportError when viewing a report
-* [#2319](https://github.com/digitalocean/netbox/issues/2319) - Extend ChoiceField to properly handle true/false choice keys
-* [#2320](https://github.com/digitalocean/netbox/issues/2320) - TypeError when dispatching a webhook with a secret key configured
-* [#2321](https://github.com/digitalocean/netbox/issues/2321) - Allow explicitly setting a null value on nullable ChoiceFields
-* [#2322](https://github.com/digitalocean/netbox/issues/2322) - Webhooks firing on non-enabled event types
-* [#2323](https://github.com/digitalocean/netbox/issues/2323) - DoesNotExist raised when deleting devices or virtual machines
-* [#2330](https://github.com/digitalocean/netbox/issues/2330) - Incorrect tab link in VRF changelog view
-
----
-
-v2.4.1 (2018-08-07)
-
-## Bug Fixes
-
-* [#2303](https://github.com/digitalocean/netbox/issues/2303) - Always redirect to parent object when bulk editing/deleting components
-* [#2308](https://github.com/digitalocean/netbox/issues/2308) - Custom fields panel absent from object view in UI
-* [#2310](https://github.com/digitalocean/netbox/issues/2310) - False validation error on certain nested serializers
-* [#2311](https://github.com/digitalocean/netbox/issues/2311) - Redirect to parent after editing interface from device/VM view
-* [#2312](https://github.com/digitalocean/netbox/issues/2312) - Running a report yields a ValueError exception
-* [#2314](https://github.com/digitalocean/netbox/issues/2314) - Serialized representation of object in change log does not include assigned tags
-
----
-
-v2.4.0 (2018-08-06)
-
-## New Features
-
-### Webhooks ([#81](https://github.com/digitalocean/netbox/issues/81))
-
-Webhooks enable NetBox to send a representation of an object every time one is created, updated, or deleted. Webhooks are sent from NetBox to external services via HTTP, and can be limited by object type. Services which receive a webhook can act on the data provided by NetBox to automate other tasks.
-
-Special thanks to [John Anderson](https://github.com/lampwins) for doing the heavy lifting for this feature!
-
-### Tagging ([#132](https://github.com/digitalocean/netbox/issues/132))
-
-Tags are free-form labels which can be assigned to a variety of objects in NetBox. Tags can be used to categorize and filter objects in addition to built-in and custom fields. Objects to which tags apply now include a `tags` field in the API.
-
-### Contextual Configuration Data ([#1349](https://github.com/digitalocean/netbox/issues/1349))
-
-Sometimes it is desirable to associate arbitrary data with a group of devices to aid in their configuration. (For example, you might want to associate a set of syslog servers for all devices at a particular site.) Context data enables the association of arbitrary data (expressed in JSON format) to devices and virtual machines grouped by region, site, role, platform, and/or tenancy. Context data is arranged hierarchically, so that data with a higher weight can be entered to override more general lower-weight data. Multiple instances of data are automatically merged by NetBox to present a single dictionary for each object.
-
-### Change Logging ([#1898](https://github.com/digitalocean/netbox/issues/1898))
-
-When an object is created, updated, or deleted, NetBox now automatically records a serialized representation of that object (similar to how it appears in the REST API) as well the event time and user account associated with the change.
-
-## Enhancements
-
-* [#238](https://github.com/digitalocean/netbox/issues/238) - Allow racks with the same name within a site (but in different groups)
-* [#971](https://github.com/digitalocean/netbox/issues/971) - Add a view to show all VLAN IDs available within a group
-* [#1673](https://github.com/digitalocean/netbox/issues/1673) - Added object/list views for services
-* [#1687](https://github.com/digitalocean/netbox/issues/1687) - Enabled custom fields for services
-* [#1739](https://github.com/digitalocean/netbox/issues/1739) - Enabled custom fields for secrets
-* [#1794](https://github.com/digitalocean/netbox/issues/1794) - Improved POST/PATCH representation of nested objects
-* [#2029](https://github.com/digitalocean/netbox/issues/2029) - Added optional NAPALM arguments to Platform model
-* [#2034](https://github.com/digitalocean/netbox/issues/2034) - Include the ID when showing nested interface connections (API change)
-* [#2118](https://github.com/digitalocean/netbox/issues/2118) - Added `latitude` and `longitude` fields to Site for GPS coordinates
-* [#2131](https://github.com/digitalocean/netbox/issues/2131) - Added `created` and `last_updated` fields to DeviceType
-* [#2157](https://github.com/digitalocean/netbox/issues/2157) - Fixed natural ordering of objects when sorted by name
-* [#2225](https://github.com/digitalocean/netbox/issues/2225) - Add "view elevations" button for site rack groups
-
-## Bug Fixes
-
-* [#2272](https://github.com/digitalocean/netbox/issues/2272) - Allow subdevice_role to be null on DeviceTypeSerializer"
-* [#2286](https://github.com/digitalocean/netbox/issues/2286) - Fixed "mark connected" button for PDU outlet connections
-
-## API Changes
-
-* Introduced the `/extras/config-contexts/`, `/extras/object-changes/`, and `/extras/tags/` API endpoints
-* API writes now return a nested representation of related objects (rather than only a numeric ID)
-* The dcim.DeviceType serializer now includes `created` and `last_updated` fields
-* The dcim.Site serializer now includes `latitude` and `longitude` fields
-* The ipam.Service and secrets.Secret serializers now include custom fields
-* The dcim.Platform serializer now includes a free-form (JSON) `napalm_args` field
-
-## Changes Since v2.4-beta1
-
-### Enhancements
-
-* [#2229](https://github.com/digitalocean/netbox/issues/2229) - Allow mapping of ConfigContexts to tenant groups
-* [#2259](https://github.com/digitalocean/netbox/issues/2259) - Add changelog tab to interface view
-* [#2264](https://github.com/digitalocean/netbox/issues/2264) - Added "map it" link for site GPS coordinates
-
-### Bug Fixes
-
-* [#2137](https://github.com/digitalocean/netbox/issues/2137) - Fixed JSON serialization of dates
-* [#2258](https://github.com/digitalocean/netbox/issues/2258) - Include changed object type on home page changelog
-* [#2265](https://github.com/digitalocean/netbox/issues/2265) - Include parent regions when filtering applicable ConfigContexts
-* [#2288](https://github.com/digitalocean/netbox/issues/2288) - Fix exception when assigning objects to a ConfigContext via the API
-* [#2296](https://github.com/digitalocean/netbox/issues/2296) - Fix AttributeError when creating a new object with tags assigned
-* [#2300](https://github.com/digitalocean/netbox/issues/2300) - Fix assignment of an interface to an IP address via API PATCH
-* [#2301](https://github.com/digitalocean/netbox/issues/2301) - Fix model validation on assignment of ManyToMany fields via API PATCH
-* [#2305](https://github.com/digitalocean/netbox/issues/2305) - Make VLAN fields optional when creating a VM interface via the API
-
----
-
-v2.3.7 (2018-07-26)
-
-## Enhancements
-
-* [#2166](https://github.com/digitalocean/netbox/issues/2166) - Enable partial matching on device asset_tag during search
-
-## Bug Fixes
-
-* [#1977](https://github.com/digitalocean/netbox/issues/1977) - Fixed exception when creating a virtual chassis with a non-master device in position 1
-* [#1992](https://github.com/digitalocean/netbox/issues/1992) - Isolate errors when one of multiple NAPALM methods fails
-* [#2202](https://github.com/digitalocean/netbox/issues/2202) - Ditched half-baked concept of tenancy inheritance via VRF
-* [#2222](https://github.com/digitalocean/netbox/issues/2222) - IP addresses created via the `available-ips` API endpoint should have the same mask as their parent prefix (not /32)
-* [#2231](https://github.com/digitalocean/netbox/issues/2231) - Remove `get_absolute_url()` from DeviceRole (can apply to devices or VMs)
-* [#2250](https://github.com/digitalocean/netbox/issues/2250) - Include stat counters on report result navigation
-* [#2255](https://github.com/digitalocean/netbox/issues/2255) - Corrected display of results in reports list
-* [#2256](https://github.com/digitalocean/netbox/issues/2256) - Prevent navigation menu overlap when jumping to test results on report page
-* [#2257](https://github.com/digitalocean/netbox/issues/2257) - Corrected casting of RIR utilization stats as floats
-* [#2266](https://github.com/digitalocean/netbox/issues/2266) - Permit additional logging of exceptions beyond custom middleware
-
----
-
-v2.3.6 (2018-07-16)
-
-## Enhancements
-
-* [#2107](https://github.com/digitalocean/netbox/issues/2107) - Added virtual chassis to global search
-* [#2125](https://github.com/digitalocean/netbox/issues/2125) - Show child status in device bay list
-
-## Bug Fixes
-
-* [#2214](https://github.com/digitalocean/netbox/issues/2214) - Error when assigning a VLAN to an interface on a VM in a cluster with no assigned site
-* [#2239](https://github.com/digitalocean/netbox/issues/2239) - Pin django-filter to version 1.1.0
-
----
-
-v2.3.5 (2018-07-02)
-
-## Enhancements
-
-* [#2159](https://github.com/digitalocean/netbox/issues/2159) - Allow custom choice field to specify a default choice
-* [#2177](https://github.com/digitalocean/netbox/issues/2177) - Include device serial number in rack elevation pop-up
-* [#2194](https://github.com/digitalocean/netbox/issues/2194) - Added `address` filter to IPAddress model
-
-## Bug Fixes
-
-* [#1826](https://github.com/digitalocean/netbox/issues/1826) - Corrected description of security parameters under API definition
-* [#2021](https://github.com/digitalocean/netbox/issues/2021) - Fix recursion error when viewing API docs under Python 3.4
-* [#2064](https://github.com/digitalocean/netbox/issues/2064) - Disable calls to online swagger validator
-* [#2173](https://github.com/digitalocean/netbox/issues/2173) - Fixed IndexError when automatically allocating IP addresses from large IPv6 prefixes
-* [#2181](https://github.com/digitalocean/netbox/issues/2181) - Raise validation error on invalid `prefix_length` when allocating next-available prefix
-* [#2182](https://github.com/digitalocean/netbox/issues/2182) - ValueError can be raised when viewing the interface connections table
-* [#2191](https://github.com/digitalocean/netbox/issues/2191) - Added missing static choices to circuits and DCIM API endpoints
-* [#2192](https://github.com/digitalocean/netbox/issues/2192) - Prevent a 0U device from being assigned to a rack position
-
----
-
-v2.3.4 (2018-06-07)
-
-## Bug Fixes
-
-* [#2066](https://github.com/digitalocean/netbox/issues/2066) - Catch `AddrFormatError` exception on invalid IP addresses
-* [#2075](https://github.com/digitalocean/netbox/issues/2075) - Enable tenant assignment when creating a rack reservation via the API
-* [#2083](https://github.com/digitalocean/netbox/issues/2083) - Add missing export button to rack roles list view
-* [#2087](https://github.com/digitalocean/netbox/issues/2087) - Don't overwrite existing vc_position of master device when creating a virtual chassis
-* [#2093](https://github.com/digitalocean/netbox/issues/2093) - Fix link to circuit termination in device interfaces table
-* [#2097](https://github.com/digitalocean/netbox/issues/2097) - Fixed queryset-based bulk deletion of clusters and regions
-* [#2098](https://github.com/digitalocean/netbox/issues/2098) - Fixed missing checkboxes for host devices in cluster view
-* [#2127](https://github.com/digitalocean/netbox/issues/2127) - Prevent non-conntectable interfaces from being connected
-* [#2143](https://github.com/digitalocean/netbox/issues/2143) - Accept null value for empty time zone field
-* [#2148](https://github.com/digitalocean/netbox/issues/2148) - Do not force timezone selection when editing sites in bulk
-* [#2150](https://github.com/digitalocean/netbox/issues/2150) - Fix display of LLDP neighbors when interface name contains a colon
-
----
-
-v2.3.3 (2018-04-19)
-
-## Enhancements
-
-* [#1990](https://github.com/digitalocean/netbox/issues/1990) - Improved search function when assigning an IP address to an interface
-
-## Bug Fixes
-
-* [#1975](https://github.com/digitalocean/netbox/issues/1975) - Correct filtering logic for custom boolean fields
-* [#1988](https://github.com/digitalocean/netbox/issues/1988) - Order interfaces naturally when bulk renaming
-* [#1993](https://github.com/digitalocean/netbox/issues/1993) - Corrected status choices in site CSV import form
-* [#1999](https://github.com/digitalocean/netbox/issues/1999) - Added missing description field to site edit form
-* [#2012](https://github.com/digitalocean/netbox/issues/2012) - Fixed deselection of an IP address as the primary IP for its parent device/VM
-* [#2014](https://github.com/digitalocean/netbox/issues/2014) - Allow assignment of VLANs to VM interfaces via the API
-* [#2019](https://github.com/digitalocean/netbox/issues/2019) - Avoid casting oversized numbers as integers
-* [#2022](https://github.com/digitalocean/netbox/issues/2022) - Show 0 for zero-value fields on CSV export
-* [#2023](https://github.com/digitalocean/netbox/issues/2023) - Manufacturer should not be a required field when importing platforms
-* [#2037](https://github.com/digitalocean/netbox/issues/2037) - Fixed IndexError exception when attempting to create a new rack reservation
-
----
-
-v2.3.2 (2018-03-22)
-
-## Enhancements
-
-* [#1586](https://github.com/digitalocean/netbox/issues/1586) - Extend bulk interface creation to support alphanumeric characters
-* [#1866](https://github.com/digitalocean/netbox/issues/1866) - Introduced AnnotatedMultipleChoiceField for filter forms
-* [#1930](https://github.com/digitalocean/netbox/issues/1930) - Switched to drf-yasg for Swagger API documentation
-* [#1944](https://github.com/digitalocean/netbox/issues/1944) - Enable assigning VLANs to virtual machine interfaces
-* [#1945](https://github.com/digitalocean/netbox/issues/1945) - Implemented a VLAN members view
-* [#1949](https://github.com/digitalocean/netbox/issues/1949) - Added a button to view elevations on rack groups list
-* [#1952](https://github.com/digitalocean/netbox/issues/1952) - Implemented a more robust mechanism for assigning VLANs to interfaces
-
-## Bug Fixes
-
-* [#1948](https://github.com/digitalocean/netbox/issues/1948) - Fix TypeError when attempting to add a member to an existing virtual chassis
-* [#1951](https://github.com/digitalocean/netbox/issues/1951) - Fix TypeError exception when importing platforms
-* [#1953](https://github.com/digitalocean/netbox/issues/1953) - Ignore duplicate IPs when calculating prefix utilization
-* [#1955](https://github.com/digitalocean/netbox/issues/1955) - Require a plaintext value when creating a new secret
-* [#1978](https://github.com/digitalocean/netbox/issues/1978) - Include all virtual chassis member interfaces in LLDP neighbors view
-* [#1980](https://github.com/digitalocean/netbox/issues/1980) - Fixed bug when trying to nullify a selection custom field under Python 2
-
----
-
-v2.3.1 (2018-03-01)
-
-## Enhancements
-
-* [#1910](https://github.com/digitalocean/netbox/issues/1910) - Added filters for cluster group and cluster type
-
-## Bug Fixes
-
-* [#1915](https://github.com/digitalocean/netbox/issues/1915) - Redirect to device view after deleting a component
-* [#1919](https://github.com/digitalocean/netbox/issues/1919) - Prevent exception when attempting to create a virtual machine without selecting devices
-* [#1921](https://github.com/digitalocean/netbox/issues/1921) - Ignore ManyToManyFields when validating a new object created via the API
-* [#1924](https://github.com/digitalocean/netbox/issues/1924) - Include VID in VLAN lists when editing an interface
-* [#1926](https://github.com/digitalocean/netbox/issues/1926) - Prevent reassignment of parent device when bulk editing VC member interfaces
-* [#1927](https://github.com/digitalocean/netbox/issues/1927) - Include all VC member interfaces on A side when creating a new interface connection
-* [#1928](https://github.com/digitalocean/netbox/issues/1928) - Fixed form validation when modifying VLANs assigned to an interface
-* [#1934](https://github.com/digitalocean/netbox/issues/1934) - Fixed exception when rendering export template on an object type with custom fields assigned
-* [#1935](https://github.com/digitalocean/netbox/issues/1935) - Correct API validation of VLANs assigned to interfaces
-* [#1936](https://github.com/digitalocean/netbox/issues/1936) - Trigger validation error when attempting to create a virtual chassis without specifying member positions
-
----
-
-v2.3.0 (2018-02-26)
-
-## New Features
-
-### Virtual Chassis ([#99](https://github.com/digitalocean/netbox/issues/99))
-
-A virtual chassis represents a set of physical devices with a shared control plane; for example, a stack of switches managed as a single device. Viewing the master device of a virtual chassis will show all member interfaces and IP addresses.
-
-### Interface VLAN Assignments ([#150](https://github.com/digitalocean/netbox/issues/150))
-
-Interfaces can now be assigned an 802.1Q mode (access or trunked) and associated with particular VLANs. Thanks to [John Anderson](https://github.com/lampwins) for his work on this!
-
-### Bulk Object Creation via the API ([#1553](https://github.com/digitalocean/netbox/issues/1553))
-
-The REST API now supports the creation of multiple objects of the same type using a single POST request. For example, to create multiple devices:
-
-```
-curl -X POST -H "Authorization: Token <TOKEN>" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/devices/ --data '[
-{"name": "device1", "device_type": 24, "device_role": 17, "site": 6},
-{"name": "device2", "device_type": 24, "device_role": 17, "site": 6},
-{"name": "device3", "device_type": 24, "device_role": 17, "site": 6},
-]'
-```
-
-Bulk creation is all-or-none: If any of the creations fails, the entire operation is rolled back.
-
-### Automatic Provisioning of Next Available Prefixes ([#1694](https://github.com/digitalocean/netbox/issues/1694))
-
-Similar to IP addresses, NetBox now supports automated provisioning of available prefixes from within a parent prefix. For example, to retrieve the next three available /28s within a parent /24:
-
-```
-curl -X POST -H "Authorization: Token <TOKEN>" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/ipam/prefixes/10153/available-prefixes/ --data '[
-{"prefix_length": 28},
-{"prefix_length": 28},
-{"prefix_length": 28}
-]'
-```
-
-If the parent prefix cannot accommodate all requested prefixes, the operation is cancelled and no new prefixes are created.
-
-### Bulk Renaming of Device/VM Components ([#1781](https://github.com/digitalocean/netbox/issues/1781))
-
-Device components (interfaces, console ports, etc.) can now be renamed in bulk via the web interface. This was implemented primarily to support the bulk renumbering of interfaces whose parent is part of a virtual chassis.
-
-## Enhancements
-
-* [#1283](https://github.com/digitalocean/netbox/issues/1283) - Added a `time_zone` field to the site model
-* [#1321](https://github.com/digitalocean/netbox/issues/1321) - Added `created` and `last_updated` fields for relevant models to their API serializers
-* [#1553](https://github.com/digitalocean/netbox/issues/1553) - Introduced support for bulk object creation via the API
-* [#1592](https://github.com/digitalocean/netbox/issues/1592) - Added tenancy assignment for rack reservations
-* [#1744](https://github.com/digitalocean/netbox/issues/1744) - Allow associating a platform with a specific manufacturer
-* [#1758](https://github.com/digitalocean/netbox/issues/1758) - Added a `status` field to the site model
-* [#1821](https://github.com/digitalocean/netbox/issues/1821) - Added a `description` field to the site model
-* [#1864](https://github.com/digitalocean/netbox/issues/1864) - Added a `status` field to the circuit model
-
-## Bug Fixes
-
-* [#1136](https://github.com/digitalocean/netbox/issues/1136) - Enforce model validation during bulk update
-* [#1645](https://github.com/digitalocean/netbox/issues/1645) - Simplified interface serialzier for IP addresses and optimized API view queryset
-* [#1838](https://github.com/digitalocean/netbox/issues/1838) - Fix KeyError when attempting to create a VirtualChassis with no devices selected
-* [#1847](https://github.com/digitalocean/netbox/issues/1847) - RecursionError when a virtual chasis master device has no name
-* [#1848](https://github.com/digitalocean/netbox/issues/1848) - Allow null value for interface encapsulation mode
-* [#1867](https://github.com/digitalocean/netbox/issues/1867) - Allow filtering on device status with multiple values
-* [#1881](https://github.com/digitalocean/netbox/issues/1881)* - Fixed bulk editing of interface 802.1Q settings
-* [#1884](https://github.com/digitalocean/netbox/issues/1884)* - Provide additional context to identify devices when creating/editing a virtual chassis
-* [#1907](https://github.com/digitalocean/netbox/issues/1907) - Allow removing an IP as the primary for a device when editing the IP directly
-
-\* New since v2.3-beta2
-
-## Breaking Changes
-
-* Constants representing device status have been renamed for clarity (for example, `STATUS_ACTIVE` is now `DEVICE_STATUS_ACTIVE`). Custom validation reports will need to be updated if they reference any of these constants.
-
-## API Changes
-
-* API creation calls now accept either a single JSON object or a list of JSON objects. If multiple objects are passed and one or more them fail validation, no objects will be created.
-* Added `created` and `last_updated` fields for objects inheriting from CreatedUpdatedModel.
-* Removed the `parent` filter for prefixes (use `within` or `within_include` instead).
-* The IP address serializer now includes only a minimal nested representation of the assigned interface (if any) and its parent device or virtual machine.
-* The rack reservation serializer now includes a nested representation of its owning user (as well as the assigned tenant, if any).
-* Added endpoints for virtual chassis and VC memberships.
-* Added `status`, `time_zone` (pytz format), and `description` fields to dcim.Site.
-* Added a `manufacturer` foreign key field on dcim.Platform.
-* Added a `status` field on circuits.Circuit.
-
----
-
-v2.2.10 (2018-02-21)
-
-## Enhancements
-
-* [#78](https://github.com/digitalocean/netbox/issues/78) - Extended topology maps to support console and power connections
-* [#1693](https://github.com/digitalocean/netbox/issues/1693) - Allow specifying loose or exact matching for custom field filters
-* [#1714](https://github.com/digitalocean/netbox/issues/1714) - Standardized CSV export functionality for all object lists
-* [#1876](https://github.com/digitalocean/netbox/issues/1876) - Added explanatory title text to disabled NAPALM buttons on device view
-* [#1885](https://github.com/digitalocean/netbox/issues/1885) - Added a device filter field for primary IP
-
-## Bug Fixes
-
-* [#1858](https://github.com/digitalocean/netbox/issues/1858) - Include device/VM count for cluster list in global search results
-* [#1859](https://github.com/digitalocean/netbox/issues/1859) - Implemented support for line breaks within CSV fields
-* [#1860](https://github.com/digitalocean/netbox/issues/1860) - Do not populate initial values for custom fields when editing objects in bulk
-* [#1869](https://github.com/digitalocean/netbox/issues/1869) - Corrected ordering of VRFs with duplicate names
-* [#1886](https://github.com/digitalocean/netbox/issues/1886) - Allow setting the primary IPv4/v6 address for a virtual machine via the web UI
-
----
-
-v2.2.9 (2018-01-31)
-
-## Enhancements
-
-* [#144](https://github.com/digitalocean/netbox/issues/144) - Implemented bulk import/edit/delete views for InventoryItems
-* [#1073](https://github.com/digitalocean/netbox/issues/1073) - Include prefixes/IPs from all VRFs when viewing the children of a container prefix in the global table
-* [#1366](https://github.com/digitalocean/netbox/issues/1366) - Enable searching for regions by name/slug
-* [#1406](https://github.com/digitalocean/netbox/issues/1406) - Display tenant description as title text in object tables
-* [#1824](https://github.com/digitalocean/netbox/issues/1824) - Add virtual machine count to platforms list
-* [#1835](https://github.com/digitalocean/netbox/issues/1835) - Consistent positioning of previous/next rack buttons
-
-## Bug Fixes
-
-* [#1621](https://github.com/digitalocean/netbox/issues/1621) - Tweaked LLDP interface name evaluation logic
-* [#1765](https://github.com/digitalocean/netbox/issues/1765) - Improved rendering of null options for model choice fields in filter forms
-* [#1807](https://github.com/digitalocean/netbox/issues/1807) - Populate VRF from parent when creating a new prefix
-* [#1809](https://github.com/digitalocean/netbox/issues/1809) - Populate tenant assignment from parent when creating a new prefix
-* [#1818](https://github.com/digitalocean/netbox/issues/1818) - InventoryItem API serializer no longer requires specifying a null value for items with no parent
-* [#1845](https://github.com/digitalocean/netbox/issues/1845) - Correct display of VMs in list with no role assigned
-* [#1850](https://github.com/digitalocean/netbox/issues/1850) - Fix TypeError when attempting IP address import if only unnamed devices exist
-
----
-
-v2.2.8 (2017-12-20)
-
-## Enhancements
-
-* [#1771](https://github.com/digitalocean/netbox/issues/1771) - Added name filter for racks
-* [#1772](https://github.com/digitalocean/netbox/issues/1772) - Added position filter for devices
-* [#1773](https://github.com/digitalocean/netbox/issues/1773) - Moved child prefixes table to its own view
-* [#1774](https://github.com/digitalocean/netbox/issues/1774) - Include a button to refine search results for all object types under global search
-* [#1784](https://github.com/digitalocean/netbox/issues/1784) - Added `cluster_type` filters for virtual machines
-
-## Bug Fixes
-
-* [#1766](https://github.com/digitalocean/netbox/issues/1766) - Fixed display of "select all" button on device power outlets list
-* [#1767](https://github.com/digitalocean/netbox/issues/1767) - Use proper template for 404 responses
-* [#1778](https://github.com/digitalocean/netbox/issues/1778) - Preserve initial VRF assignment when adding IP addresses in bulk from a prefix
-* [#1783](https://github.com/digitalocean/netbox/issues/1783) - Added `vm_role` filter for device roles
-* [#1785](https://github.com/digitalocean/netbox/issues/1785) - Omit filter forms from browsable API
-* [#1787](https://github.com/digitalocean/netbox/issues/1787) - Added missing site field to virtualization cluster CSV export
-
----
-
-v2.2.7 (2017-12-07)
-
-## Enhancements
-
-* [#1722](https://github.com/digitalocean/netbox/issues/1722) - Added virtual machine count to site view
-* [#1737](https://github.com/digitalocean/netbox/issues/1737) - Added a `contains` API filter to find all prefixes containing a given IP or prefix
-
-## Bug Fixes
-
-* [#1712](https://github.com/digitalocean/netbox/issues/1712) - Corrected tenant inheritance for new IP addresses created from a parent prefix
-* [#1721](https://github.com/digitalocean/netbox/issues/1721) - Differentiated child IP count from utilization percentage for prefixes
-* [#1740](https://github.com/digitalocean/netbox/issues/1740) - Delete session_key cookie on logout
-* [#1741](https://github.com/digitalocean/netbox/issues/1741) - Fixed Unicode support for secret plaintexts
-* [#1743](https://github.com/digitalocean/netbox/issues/1743) - Include number of instances for device types in global search
-* [#1751](https://github.com/digitalocean/netbox/issues/1751) - Corrected filtering for IPv6 addresses containing letters
-* [#1756](https://github.com/digitalocean/netbox/issues/1756) - Improved natural ordering of console server ports and power outlets
-
----
-
-v2.2.6 (2017-11-16)
-
-## Enhancements
-
-* [#1669](https://github.com/digitalocean/netbox/issues/1669) - Clicking "add an IP" from the prefix view will default to the first available IP within the prefix
-
-## Bug Fixes
-
-* [#1397](https://github.com/digitalocean/netbox/issues/1397) - Display global search in navigation menu unless display is less than 1200px wide
-* [#1599](https://github.com/digitalocean/netbox/issues/1599) - Reduce mobile cut-off for navigation menu to 960px
-* [#1715](https://github.com/digitalocean/netbox/issues/1715) - Added missing import buttons on object lists
-* [#1717](https://github.com/digitalocean/netbox/issues/1717) - Fixed interface validation for virtual machines
-* [#1718](https://github.com/digitalocean/netbox/issues/1718) - Set empty label to "Global" or VRF field in IP assignment form
-
----
-
-v2.2.5 (2017-11-14)
-
-## Enhancements
-
-* [#1512](https://github.com/digitalocean/netbox/issues/1512) - Added a view to search for an IP address being assigned to an interface
-* [#1679](https://github.com/digitalocean/netbox/issues/1679) - Added IP address roles to device/VM interface lists
-* [#1683](https://github.com/digitalocean/netbox/issues/1683) - Replaced default 500 handler with custom middleware to provide preliminary troubleshooting assistance
-* [#1684](https://github.com/digitalocean/netbox/issues/1684) - Replaced prefix `parent` filter with `within` and `within_include`
-
-## Bug Fixes
-
-* [#1471](https://github.com/digitalocean/netbox/issues/1471) - Correct bulk selection of IP addresses within a prefix assigned to a VRF
-* [#1642](https://github.com/digitalocean/netbox/issues/1642) - Validate device type classification when creating console server ports and power outlets
-* [#1650](https://github.com/digitalocean/netbox/issues/1650) - Correct numeric ordering for interfaces with no alphabetic type
-* [#1676](https://github.com/digitalocean/netbox/issues/1676) - Correct filtering of child prefixes upon bulk edit/delete from the parent prefix view
-* [#1689](https://github.com/digitalocean/netbox/issues/1689) - Disregard IP address mask when filtering for child IPs of a prefix
-* [#1696](https://github.com/digitalocean/netbox/issues/1696) - Fix for NAPALM v2.0+
-* [#1699](https://github.com/digitalocean/netbox/issues/1699) - Correct nested representation in the API of primary IPs for virtual machines and add missing primary_ip property
-* [#1701](https://github.com/digitalocean/netbox/issues/1701) - Fixed validation in `extras/0008_reports.py` migration for certain versions of PostgreSQL
-* [#1703](https://github.com/digitalocean/netbox/issues/1703) - Added API serializer validation for custom integer fields
-* [#1705](https://github.com/digitalocean/netbox/issues/1705) - Fixed filtering of devices with a status of offline
-
----
-
-v2.2.4 (2017-10-31)
-
-## Bug Fixes
-
-* [#1670](https://github.com/digitalocean/netbox/issues/1670) - Fixed server error when calling certain filters (regression from #1649)
-
----
-
-v2.2.3 (2017-10-31)
-
-## Enhancements
-
-* [#999](https://github.com/digitalocean/netbox/issues/999) - Display devices on which circuits are terminated in circuits list
-* [#1491](https://github.com/digitalocean/netbox/issues/1491) - Added initial data for the virtualization app
-* [#1620](https://github.com/digitalocean/netbox/issues/1620) - Loosen IP address search filter to match all IPs that start with the given string
-* [#1631](https://github.com/digitalocean/netbox/issues/1631) - Added a `post_run` method to the Report class
-* [#1666](https://github.com/digitalocean/netbox/issues/1666) - Allow modifying the owner of a rack reservation
-
-## Bug Fixes
-
-* [#1513](https://github.com/digitalocean/netbox/issues/1513) - Correct filtering of custom field choices
-* [#1603](https://github.com/digitalocean/netbox/issues/1603) - Hide selection checkboxes for tables with no available actions
-* [#1618](https://github.com/digitalocean/netbox/issues/1618) - Allow bulk deletion of all virtual machines
-* [#1619](https://github.com/digitalocean/netbox/issues/1619) - Correct text-based filtering of IP network and address fields
-* [#1624](https://github.com/digitalocean/netbox/issues/1624) - Add VM count to device roles table
-* [#1634](https://github.com/digitalocean/netbox/issues/1634) - Cluster should not be a required field when importing child devices
-* [#1649](https://github.com/digitalocean/netbox/issues/1649) - Correct filtering on null values (e.g. ?tenant_id=0) for django-filters v1.1.0+
-* [#1653](https://github.com/digitalocean/netbox/issues/1653) - Remove outdated description for DeviceType's `is_network_device` flag
-* [#1664](https://github.com/digitalocean/netbox/issues/1664) - Added missing `serial` field in default rack CSV export
-
----
-
-v2.2.2 (2017-10-17)
-
-## Enhancements
-
-* [#1580](https://github.com/digitalocean/netbox/issues/1580) - Allow cluster assignment when bulk importing devices
-* [#1587](https://github.com/digitalocean/netbox/issues/1587) - Add primary IP column for virtual machines in global search results
-
-## Bug Fixes
-
-* [#1498](https://github.com/digitalocean/netbox/issues/1498) - Avoid duplicating nodes when generating topology maps
-* [#1579](https://github.com/digitalocean/netbox/issues/1579) - Devices already assigned to a cluster cannot be added to a different cluster
-* [#1582](https://github.com/digitalocean/netbox/issues/1582) - Add `virtual_machine` attribute to IPAddress
-* [#1584](https://github.com/digitalocean/netbox/issues/1584) - Colorized virtual machine role column
-* [#1585](https://github.com/digitalocean/netbox/issues/1585) - Fixed slug-based filtering of virtual machines
-* [#1605](https://github.com/digitalocean/netbox/issues/1605) - Added clusters and virtual machines to object list for global search
-* [#1609](https://github.com/digitalocean/netbox/issues/1609) - Added missing `virtual_machine` field to IP address interface serializer
-
----
-
-v2.2.1 (2017-10-12)
-
-## Bug Fixes
-
-* [#1576](https://github.com/digitalocean/netbox/issues/1576) - Moved PostgreSQL validation logic into the relevant migration (fixed ImproperlyConfigured exception on init)
-
----
-
-v2.2.0 (2017-10-12)
-
-**Note:** This release requires PostgreSQL 9.4 or higher. Do not attempt to upgrade unless you are running at least PostgreSQL 9.4.
-
-**Note:** The release replaces the deprecated pycrypto library with [pycryptodome](https://github.com/Legrandin/pycryptodome). The upgrade script has been extended to automatically uninstall the old library, but please verify your installed packages with `pip freeze | grep pycrypto` if you run into problems.
-
-## New Features
-
-### Virtual Machines and Clusters ([#142](https://github.com/digitalocean/netbox/issues/142))
-
-Our second-most popular feature request has arrived! NetBox now supports the creation of virtual machines, which can be assigned virtual interfaces and IP addresses. VMs are arranged into clusters, each of which has a type and (optionally) a group.
-
-### Custom Validation Reports ([#1511](https://github.com/digitalocean/netbox/issues/1511))
-
-Users can now create custom reports which are run to validate data in NetBox. Reports work very similar to Python unit tests: Each report inherits from NetBox's Report class and contains one or more test method. Reports can be run and retrieved via the web UI, API, or CLI. See [the docs](http://netbox.readthedocs.io/en/stable/miscellaneous/reports/) for more info.
-
-## Enhancements
-
-* [#494](https://github.com/digitalocean/netbox/issues/494) - Include asset tag in device info pop-up on rack elevation
-* [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added a `serial` field to the rack model
-* [#1479](https://github.com/digitalocean/netbox/issues/1479) - Added an IP address role for CARP
-* [#1506](https://github.com/digitalocean/netbox/issues/1506) - Extended rack facility ID field from 30 to 50 characters
-* [#1510](https://github.com/digitalocean/netbox/issues/1510) - Added ability to search by name when adding devices to a cluster
-* [#1527](https://github.com/digitalocean/netbox/issues/1527) - Replace deprecated pycrypto library with pycryptodome
-* [#1551](https://github.com/digitalocean/netbox/issues/1551) - Added API endpoints listing static field choices for each app
-* [#1556](https://github.com/digitalocean/netbox/issues/1556) - Added CPAK, CFP2, and CFP4 100GE interface form factors
-* Added CSV import views for all object types
-
-## Bug Fixes
-
-* [#1550](https://github.com/digitalocean/netbox/issues/1550) - Corrected interface connections link in navigation menu
-* [#1554](https://github.com/digitalocean/netbox/issues/1554) - Don't require form_factor when creating an interface assigned to a virtual machine
-* [#1557](https://github.com/digitalocean/netbox/issues/1557) - Added filtering for virtual machine interfaces
-* [#1567](https://github.com/digitalocean/netbox/issues/1567) - Prompt user for session key when importing secrets
-
-## API Changes
-
-* Introduced the virtualization app and its associated endpoints at `/api/virtualization`
-* Added the `/api/extras/reports` endpoint for fetching and running reports
-* The `ipam.Service` and `dcim.Interface` models now have a `virtual_machine` field in addition to the `device` field. Only one of the two fields may be defined for each object
-* Added a `vm_role` field to `dcim.DeviceRole`, which indicates whether a role is suitable for assigned to a virtual machine
-* Added a `serial` field to 'dcim.Rack` for serial numbers
-* Each app now has a `_choices` endpoint, which lists the available options for all model field with static choices (e.g. interface form factors)
-
----
-
-v2.1.6 (2017-10-11)
-
-## Enhancements
-
-* [#1548](https://github.com/digitalocean/netbox/issues/1548) - Automatically populate tenant assignment when adding an IP address from the prefix view
-* [#1561](https://github.com/digitalocean/netbox/issues/1561) - Added primary IP to the devices table in global search
-* [#1563](https://github.com/digitalocean/netbox/issues/1563) - Made necessary updates for Django REST Framework v3.7.0
-
----
-
-v2.1.5 (2017-09-25)
-
-## Enhancements
-
-* [#1484](https://github.com/digitalocean/netbox/issues/1484) - Added individual "add VLAN" buttons on the VLAN groups list
-* [#1485](https://github.com/digitalocean/netbox/issues/1485) - Added `BANNER_LOGIN` configuration setting to display a banner on the login page
-* [#1499](https://github.com/digitalocean/netbox/issues/1499) - Added utilization graph to child prefixes table
-* [#1523](https://github.com/digitalocean/netbox/issues/1523) - Improved the natural ordering of interfaces (thanks to [@tarkatronic](https://github.com/tarkatronic))
-* [#1536](https://github.com/digitalocean/netbox/issues/1536) - Improved formatting of aggregate prefix statistics
-
-## Bug Fixes
-
-* [#1469](https://github.com/digitalocean/netbox/issues/1469) - Allow a NAT IP to be assigned as the primary IP for a device
-* [#1472](https://github.com/digitalocean/netbox/issues/1472) - Prevented truncation when displaying secret strings containing HTML characters
-* [#1486](https://github.com/digitalocean/netbox/issues/1486) - Ignore subinterface IDs when validating LLDP neighbor connections
-* [#1489](https://github.com/digitalocean/netbox/issues/1489) - Corrected server error on validation of empty required custom field
-* [#1507](https://github.com/digitalocean/netbox/issues/1507) - Fixed error when creating the next available IP from a prefix within a VRF
-* [#1520](https://github.com/digitalocean/netbox/issues/1520) - Redirect on GET request to bulk edit/delete views
-* [#1522](https://github.com/digitalocean/netbox/issues/1522) - Removed object create/edit forms from the browsable API
-
----
-
-v2.1.4 (2017-08-30)
-
-## Enhancements
-
-* [#1326](https://github.com/digitalocean/netbox/issues/1326) - Added dropdown widget with common values for circuit speed fields
-* [#1341](https://github.com/digitalocean/netbox/issues/1341) - Added a `MEDIA_ROOT` configuration setting to specify where uploaded files are stored on disk
-* [#1376](https://github.com/digitalocean/netbox/issues/1376) - Ignore anycast addresses when detecting duplicate IPs
-* [#1402](https://github.com/digitalocean/netbox/issues/1402) - Increased max length of name field for device components
-* [#1431](https://github.com/digitalocean/netbox/issues/1431) - Added interface form factor for 10GBASE-CX4
-* [#1432](https://github.com/digitalocean/netbox/issues/1432) - Added a `commit_rate` field to the circuits list search form
-* [#1460](https://github.com/digitalocean/netbox/issues/1460) - Hostnames with no domain are now acceptable in custom URL fields
-
-## Bug Fixes
-
-* [#1429](https://github.com/digitalocean/netbox/issues/1429) - Fixed uptime formatting on device status page
-* [#1433](https://github.com/digitalocean/netbox/issues/1433) - Fixed `devicetype_id` filter for DeviceType components
-* [#1443](https://github.com/digitalocean/netbox/issues/1443) - Fixed API validation error involving custom field data
-* [#1458](https://github.com/digitalocean/netbox/issues/1458) - Corrected permission name on prefix/VLAN roles list
-
----
-
-v2.1.3 (2017-08-15)
-
-## Bug Fixes
-
-* [#1330](https://github.com/digitalocean/netbox/issues/1330) - Raise validation error when assigning an unrelated IP as the primary IP for a device
-* [#1389](https://github.com/digitalocean/netbox/issues/1389) - Avoid splitting carat/prefix on prefix list
-* [#1400](https://github.com/digitalocean/netbox/issues/1400) - Removed redundant display of assigned device interface from IP address list
-* [#1414](https://github.com/digitalocean/netbox/issues/1414) - Selecting a site from the rack filters automatically updates the available rack groups
-* [#1419](https://github.com/digitalocean/netbox/issues/1419) - Allow editing image attachments without re-uploading an image
-* [#1420](https://github.com/digitalocean/netbox/issues/1420) - Exclude virtual interfaces from device LLDP neighbors view
-* [#1421](https://github.com/digitalocean/netbox/issues/1421) - Improved model validation logic for API serializers
-* Fixed page title capitalization in the browsable API
-
----
-
-v2.1.2 (2017-08-04)
-
-## Enhancements
-
-* [#992](https://github.com/digitalocean/netbox/issues/992) - Allow the creation of multiple services per device with the same protocol and port
-* Tweaked navigation menu styling
-
-## Bug Fixes
-
-* [#1388](https://github.com/digitalocean/netbox/issues/1388) - Fixed server error when searching globally for IPs/prefixes (rolled back #1379)
-* [#1390](https://github.com/digitalocean/netbox/issues/1390) - Fixed IndexError when viewing available IPs within large IPv6 prefixes
-
----
-
-v2.1.1 (2017-08-02)
-
-## Enhancements
-
-* [#893](https://github.com/digitalocean/netbox/issues/893) - Allow filtering by null values for NullCharacterFields (e.g. return only unnamed devices)
-* [#1368](https://github.com/digitalocean/netbox/issues/1368) - Render reservations in rack elevations view
-* [#1374](https://github.com/digitalocean/netbox/issues/1374) - Added NAPALM_ARGS and NAPALM_TIMEOUT configiuration parameters
-* [#1375](https://github.com/digitalocean/netbox/issues/1375) - Renamed `NETBOX_USERNAME` and `NETBOX_PASSWORD` configuration parameters to `NAPALM_USERNAME` and `NAPALM_PASSWORD`
-* [#1379](https://github.com/digitalocean/netbox/issues/1379) - Allow searching devices by interface MAC address in global search
-
-## Bug Fixes
-
-* [#461](https://github.com/digitalocean/netbox/issues/461) - Display a validation error when attempting to assigning a new child device to a rack face/position
-* [#1385](https://github.com/digitalocean/netbox/issues/1385) - Connected device API endpoint no longer requires authentication if `LOGIN_REQUIRED` is False
-
----
-
-v2.1.0 (2017-07-25)
-
-## New Features
-
-### IP Address Roles ([#819](https://github.com/digitalocean/netbox/issues/819))
-
-The IP address model now supports the assignment of a functional role to help identify special-purpose IPs. These include:
-
-* Loopback
-* Secondary
-* Anycast
-* VIP
-* VRRP
-* HSRP
-* GLBP
-
-### Automatic Provisioning of Next Available IP ([#1246](https://github.com/digitalocean/netbox/issues/1246))
-
-A new API endpoint has been added at `/api/ipam/prefixes/<pk>/available-ips/`. A GET request to this endpoint will return a list of available IP addresses within the prefix (up to the pagination limit). A POST request will automatically create and return the next available IP address.
-
-### NAPALM Integration ([#1348](https://github.com/digitalocean/netbox/issues/1348))
-
-The [NAPALM automation](https://napalm-automation.net/) library provides an abstracted interface for pulling live data (e.g. uptime, software version, running config, LLDP neighbors, etc.) from network devices. The NetBox API has been extended to support executing read-only NAPALM methods on devices defined in NetBox. To enable this functionality, ensure that NAPALM has been installed (`pip install napalm`) and the `NETBOX_USERNAME` and `NETBOX_PASSWORD` [configuration parameters](http://netbox.readthedocs.io/en/stable/configuration/optional-settings/#netbox_username) have been set in configuration.py.
-
-## Enhancements
-
-* [#838](https://github.com/digitalocean/netbox/issues/838) - Display details of all objects being edited/deleted in bulk
-* [#1041](https://github.com/digitalocean/netbox/issues/1041) - Added enabled and MTU fields to the interface model
-* [#1121](https://github.com/digitalocean/netbox/issues/1121) - Added asset_tag and description fields to the InventoryItem model
-* [#1141](https://github.com/digitalocean/netbox/issues/1141) - Include RD when listing VRFs in a form selection field
-* [#1203](https://github.com/digitalocean/netbox/issues/1203) - Implemented query filters for all models
-* [#1218](https://github.com/digitalocean/netbox/issues/1218) - Added IEEE 802.11 wireless interface types
-* [#1269](https://github.com/digitalocean/netbox/issues/1269) - Added circuit termination to interface serializer
-* [#1320](https://github.com/digitalocean/netbox/issues/1320) - Removed checkbox from confirmation dialog
-
-## Bug Fixes
-
-* [#1079](https://github.com/digitalocean/netbox/issues/1079) - Order interfaces naturally via API
-* [#1285](https://github.com/digitalocean/netbox/issues/1285) - Enforce model validation when creating/editing objects via the API
-* [#1358](https://github.com/digitalocean/netbox/issues/1358) - Correct VRF example values in IP/prefix import forms
-* [#1362](https://github.com/digitalocean/netbox/issues/1362) - Raise validation error when attempting to create an API key that's too short
-* [#1371](https://github.com/digitalocean/netbox/issues/1371) - Extend DeviceSerializer.parent_device to include standard fields
-
-## API changes
-
-* Added a new API endpoint which makes [NAPALM](https://github.com/napalm-automation/napalm) accessible via NetBox
-* Device components (console ports, power ports, interfaces, etc.) can only be filtered by a single device name or ID. This limitation was necessary to allow the natural ordering of interfaces according to the device's parent device type.
-* Added two new fields to the interface serializer: `enabled` (boolean) and `mtu` (unsigned integer)
-* Modified the interface serializer to include three discrete fields relating to connections: `is_connected` (boolean), `interface_connection`, and `circuit_termination`
-* Added two new fields to the inventory item serializer: `asset_tag` and `description`
-* Added "wireless" to interface type filter (in addition to physical, virtual, and LAG)
-* Added a new endpoint at /api/ipam/prefixes/<pk>/available-ips/ to retrieve or create available IPs within a prefix
-* Extended `parent_device` on DeviceSerializer to include the `url` and `display_name` of the parent Device, and the `url` of the DeviceBay
-
----
-
-v2.0.10 (2017-07-14)
-
-## Bug Fixes
-
-* [#1312](https://github.com/digitalocean/netbox/issues/1312) - Catch error when attempting to activate a user key with an invalid private key
-* [#1333](https://github.com/digitalocean/netbox/issues/1333) - Corrected label on is_console_server field of DeviceType bulk edit form
-* [#1338](https://github.com/digitalocean/netbox/issues/1338) - Allow importing prefixes with "container" status
-* [#1339](https://github.com/digitalocean/netbox/issues/1339) - Fixed disappearing checkbox column under django-tables2 v1.7+
-* [#1342](https://github.com/digitalocean/netbox/issues/1342) - Allow designation of users and groups when creating/editing a secret role
-
----
-
-v2.0.9 (2017-07-10)
-
-## Bug Fixes
-
-* [#1319](https://github.com/digitalocean/netbox/issues/1319) - Fixed server error when attempting to create console/power connections
-* [#1325](https://github.com/digitalocean/netbox/issues/1325) - Retain interface attachment when editing a circuit termination
-
----
-
-v2.0.8 (2017-07-05)
-
-## Enhancements
-
-* [#1298](https://github.com/digitalocean/netbox/issues/1298) - Calculate prefix utilization based on its status (container or non-container)
-* [#1303](https://github.com/digitalocean/netbox/issues/1303) - Highlight installed interface connections in green on device view
-* [#1315](https://github.com/digitalocean/netbox/issues/1315) - Enforce lowercase file extensions for image attachments
-
-## Bug Fixes
-
-* [#1279](https://github.com/digitalocean/netbox/issues/1279) - Fix primary_ip assignment during IP address import
-* [#1281](https://github.com/digitalocean/netbox/issues/1281) - Show LLDP neighbors tab on device view only if necessary conditions are met
-* [#1282](https://github.com/digitalocean/netbox/issues/1282) - Fixed tooltips on "mark connected/planned" toggle buttons for device connections
-* [#1288](https://github.com/digitalocean/netbox/issues/1288) - Corrected permission name for deleting image attachments
-* [#1289](https://github.com/digitalocean/netbox/issues/1289) - Retain inside NAT assignment when editing an IP address
-* [#1297](https://github.com/digitalocean/netbox/issues/1297) - Allow passing custom field choice selection PKs to API as string-quoted integers
-* [#1299](https://github.com/digitalocean/netbox/issues/1299) - Corrected permission name for adding services to devices
-
----
-
-v2.0.7 (2017-06-15)
-
-## Enhancements
-
-* [#626](https://github.com/digitalocean/netbox/issues/626) - Added bulk disconnect function for console/power/interface connections on device view
-
-## Bug Fixes
-
-* [#1238](https://github.com/digitalocean/netbox/issues/1238) - Fix error when editing an IP with a NAT assignment which has no assigned device
-* [#1263](https://github.com/digitalocean/netbox/issues/1263) - Differentiate add and edit permissions for objects
-* [#1265](https://github.com/digitalocean/netbox/issues/1265) - Fix console/power/interface connection validation when selecting a device via live search
-* [#1266](https://github.com/digitalocean/netbox/issues/1266) - Prevent terminating a circuit to an already-connected interface
-* [#1268](https://github.com/digitalocean/netbox/issues/1268) - Fix CSV import error under Python 3
-* [#1273](https://github.com/digitalocean/netbox/issues/1273) - Corrected status choices in IP address import form
-* [#1274](https://github.com/digitalocean/netbox/issues/1274) - Exclude unterminated circuits from topology maps
-* [#1275](https://github.com/digitalocean/netbox/issues/1275) - Raise validation error on prefix import when multiple VLANs are found
-
----
-
-v2.0.6 (2017-06-12)
-
-## Enhancements
-
-* [#40](https://github.com/digitalocean/netbox/issues/40) - Added IP utilization graph to prefix list
-* [#704](https://github.com/digitalocean/netbox/issues/704) - Allow filtering VLANs by group when editing prefixes
-* [#913](https://github.com/digitalocean/netbox/issues/913) - Added headers to object CSV exports
-* [#990](https://github.com/digitalocean/netbox/issues/990) - Enable logging configuration in configuration.py
-* [#1180](https://github.com/digitalocean/netbox/issues/1180) - Simplified the process of finding related devices when viewing a device
-
-## Bug Fixes
-
-* [#1253](https://github.com/digitalocean/netbox/issues/1253) - Improved `upgrade.sh` to allow forcing Python2
-
----
-
-v2.0.5 (2017-06-08)
-
-## Notes
-
-The maximum number of objects an API consumer can request has been set to 1000 (e.g. `?limit=1000`). This limit can be modified by defining `MAX_PAGE_SIZE` in confgiuration.py. (To remove this limit, set `MAX_PAGE_SIZE=0`.)
-
-## Enhancements
-
-* [#655](https://github.com/digitalocean/netbox/issues/655) - Implemented header-based CSV import of objects
-* [#1190](https://github.com/digitalocean/netbox/issues/1190) - Allow partial string matching when searching on custom fields
-* [#1237](https://github.com/digitalocean/netbox/issues/1237) - Enabled setting limit=0 to disable pagination in API requests; added `MAX_PAGE_SIZE` configuration setting
-
-## Bug Fixes
-
-* [#837](https://github.com/digitalocean/netbox/issues/837) - Enforce uniqueness where applicable during bulk import of IP addresses
-* [#1226](https://github.com/digitalocean/netbox/issues/1226) - Improved validation for custom field values submitted via the API
-* [#1232](https://github.com/digitalocean/netbox/issues/1232) - Improved rack space validation on bulk import of devices (see #655)
-* [#1235](https://github.com/digitalocean/netbox/issues/1235) - Fix permission name for adding/editing inventory items
-* [#1236](https://github.com/digitalocean/netbox/issues/1236) - Truncate rack names in elevations list; add facility ID
-* [#1239](https://github.com/digitalocean/netbox/issues/1239) - Fix server error when creating VLANGroup via API
-* [#1243](https://github.com/digitalocean/netbox/issues/1243) - Catch ValueError in IP-based object filters
-* [#1244](https://github.com/digitalocean/netbox/issues/1244) - Corrected "device" secrets filter to accept a device name
-
----
-
-v2.0.4 (2017-05-25)
-
-## Bug Fixes
-
-* [#1206](https://github.com/digitalocean/netbox/issues/1206) - Fix redirection in admin UI after activating secret keys when BASE_PATH is set
-* [#1207](https://github.com/digitalocean/netbox/issues/1207) - Include nested LAG serializer when showing interface connections (API)
-* [#1210](https://github.com/digitalocean/netbox/issues/1210) - Fix TemplateDoesNotExist errors on browsable API views
-* [#1212](https://github.com/digitalocean/netbox/issues/1212) - Allow assigning new VLANs to global VLAN groups
-* [#1213](https://github.com/digitalocean/netbox/issues/1213) - Corrected table header ordering links on object list views
-* [#1214](https://github.com/digitalocean/netbox/issues/1214) - Add status to list of required fields on child device import form
-* [#1219](https://github.com/digitalocean/netbox/issues/1219) - Fix image attachment URLs when BASE_PATH is set
-* [#1220](https://github.com/digitalocean/netbox/issues/1220) - Suppressed innocuous warning about untracked migrations under Python 3
-* [#1229](https://github.com/digitalocean/netbox/issues/1229) - Fix validation error on forms where API search is used
-
----
-
-v2.0.3 (2017-05-18)
-
-## Enhancements
-
-* [#1196](https://github.com/digitalocean/netbox/issues/1196) - Added a lag_id filter to the API interfaces view
-* [#1198](https://github.com/digitalocean/netbox/issues/1198) - Allow filtering unracked devices on device list
-
-## Bug Fixes
-
-* [#1157](https://github.com/digitalocean/netbox/issues/1157) - Hide nav menu search bar on small displays
-* [#1186](https://github.com/digitalocean/netbox/issues/1186) - Corrected VLAN edit form so that site assignment is not required
-* [#1187](https://github.com/digitalocean/netbox/issues/1187) - Fixed table pagination by introducing a custom table template
-* [#1188](https://github.com/digitalocean/netbox/issues/1188) - Serialize interface LAG as nested objected (API)
-* [#1189](https://github.com/digitalocean/netbox/issues/1189) - Enforce consistent ordering of objects returned by a global search
-* [#1191](https://github.com/digitalocean/netbox/issues/1191) - Bulk selection of IPs under a prefix incorrect when "select all" is used
-* [#1195](https://github.com/digitalocean/netbox/issues/1195) - Unable to create an interface connection when searching for peer device
-* [#1197](https://github.com/digitalocean/netbox/issues/1197) - Fixed status assignment during bulk import of devices, prefixes, IPs, and VLANs
-* [#1199](https://github.com/digitalocean/netbox/issues/1199) - Bulk import of secrets does not prompt user to generate a session key
-* [#1200](https://github.com/digitalocean/netbox/issues/1200) - Form validation error when connecting power ports to power outlets
-
----
-
-v2.0.2 (2017-05-15)
-
-## Enhancements
-
-* [#1122](https://github.com/digitalocean/netbox/issues/1122) - Include NAT inside IPs in IP address list
-* [#1137](https://github.com/digitalocean/netbox/issues/1137) - Allow filtering devices list by rack
-* [#1170](https://github.com/digitalocean/netbox/issues/1170) - Include A and Z sites for circuits in global search results
-* [#1172](https://github.com/digitalocean/netbox/issues/1172) - Linkify racks in side-by-side elevations view
-* [#1177](https://github.com/digitalocean/netbox/issues/1177) - Render planned connections as dashed lines on topology maps
-* [#1179](https://github.com/digitalocean/netbox/issues/1179) - Adjust topology map text color based on node background
-* On all object edit forms, allow filtering the tenant list by tenant group
-
-## Bug Fixes
-
-* [#1158](https://github.com/digitalocean/netbox/issues/1158) - Exception thrown when creating a device component with an invalid name
-* [#1159](https://github.com/digitalocean/netbox/issues/1159) - Only superusers can see "edit IP" buttons on the device interfaces list
-* [#1160](https://github.com/digitalocean/netbox/issues/1160) - Linkify secrets and tenants in global search results
-* [#1161](https://github.com/digitalocean/netbox/issues/1161) - Fix "add another" behavior when creating an API token
-* [#1166](https://github.com/digitalocean/netbox/issues/1166) - Fixed bulk IP address creation when assigning tenants
-* [#1168](https://github.com/digitalocean/netbox/issues/1168) - Total count of objects missing from list view paginator
-* [#1171](https://github.com/digitalocean/netbox/issues/1171) - Allow removing site assignment when bulk editing VLANs
-* [#1173](https://github.com/digitalocean/netbox/issues/1173) - Tweak interface manager to fall back to naive ordering
-
----
-
-v2.0.1 (2017-05-10)
-
-## Bug Fixes
-
-* [#1149](https://github.com/digitalocean/netbox/issues/1149) - Port list does not populate when creating a console or power connection
-* [#1150](https://github.com/digitalocean/netbox/issues/1150) - Error when uploading image attachments with Unicode names under Python 2
-* [#1151](https://github.com/digitalocean/netbox/issues/1151) - Server error: name 'escape' is not defined
-* [#1152](https://github.com/digitalocean/netbox/issues/1152) - Unable to edit user keys
-* [#1153](https://github.com/digitalocean/netbox/issues/1153) - UnicodeEncodeError when searching for non-ASCII characters on Python 2
-
----
-
-v2.0.0 (2017-05-09)
-
-## New Features
-
-### API 2.0 ([#113](https://github.com/digitalocean/netbox/issues/113))
-
-The NetBox API has been completely rewritten and now features full read/write ability.
-
-### Image Attachments ([#152](https://github.com/digitalocean/netbox/issues/152))
-
-Users are now able to attach photos and other images to sites, racks, and devices. (Please ensure that the new `media` directory is writable by the system account NetBox runs as.)
-
-### Global Search ([#159](https://github.com/digitalocean/netbox/issues/159))
-
-NetBox now supports searching across all primary object types at once.
-
-### Rack Elevations View ([#951](https://github.com/digitalocean/netbox/issues/951))
-
-A new view has been introduced to display the elevations of multiple racks side-by-side.
-
-## Enhancements
-
-* [#154](https://github.com/digitalocean/netbox/issues/154) - Expanded device status field to include options other than active/offline
-* [#430](https://github.com/digitalocean/netbox/issues/430) - Include circuits when rendering topology maps
-* [#578](https://github.com/digitalocean/netbox/issues/578) - Show topology maps not assigned to a site on the home view
-* [#1100](https://github.com/digitalocean/netbox/issues/1100) - Add a "view all" link to completed bulk import views is_pool for prefixes)
-* [#1110](https://github.com/digitalocean/netbox/issues/1110) - Expand bulk edit forms to include boolean fields (e.g. toggle is_pool for prefixes)
-
-## Bug Fixes
-
-From v1.9.6:
-
-* [#403](https://github.com/digitalocean/netbox/issues/403) - Record console/power/interface connects and disconnects as user actions
-* [#853](https://github.com/digitalocean/netbox/issues/853) -  Added "status" field to device bulk import form
-* [#1101](https://github.com/digitalocean/netbox/issues/1101) - Fix AJAX scripting for device component selection forms
-* [#1103](https://github.com/digitalocean/netbox/issues/1103) - Correct handling of validation errors when creating IP addresses in bulk
-* [#1104](https://github.com/digitalocean/netbox/issues/1104) - Fix VLAN assignment on prefix import
-* [#1115](https://github.com/digitalocean/netbox/issues/1115) - Enabled responsive (side-scrolling) tables for small screens
-* [#1116](https://github.com/digitalocean/netbox/issues/1116) - Correct object links on recursive deletion error
-* [#1125](https://github.com/digitalocean/netbox/issues/1125) - Include MAC addresses on a device's interface list
-* [#1144](https://github.com/digitalocean/netbox/issues/1144) - Allow multiple status selections for Prefix, IP address, and VLAN filters
-
-From beta3:
-
-* [#1113](https://github.com/digitalocean/netbox/issues/1113) - Fixed server error when attempting to delete an image attachment
-* [#1114](https://github.com/digitalocean/netbox/issues/1114) - Suppress OSError when attempting to access a deleted image attachment
-* [#1126](https://github.com/digitalocean/netbox/issues/1126) - Fixed server error when editing a user key via admin UI attachment
-* [#1132](https://github.com/digitalocean/netbox/issues/1132) - Prompt user to unlock session key when importing secrets
-
-## Additional Changes
-
-* The Module DCIM model has been renamed to InventoryItem to better reflect its intended function, and to make room for work on [#824](https://github.com/digitalocean/netbox/issues/824).
-* Redundant portions of the admin UI have been removed ([#973](https://github.com/digitalocean/netbox/issues/973)).
-* The Docker build components have been moved into [their own repository](https://github.com/digitalocean/netbox-docker).
-
----
-
-v1.9.6 (2017-04-21)
-
-## Improvements
-
-* [#878](https://github.com/digitalocean/netbox/issues/878) - Merged IP addresses with interfaces list on device view
-* [#1001](https://github.com/digitalocean/netbox/issues/1001) - Interface assignment can be modified when editing an IP address
-* [#1084](https://github.com/digitalocean/netbox/issues/1084) - Include custom fields when creating IP addresses in bulk
-
-## Bug Fixes
-
-* [#1057](https://github.com/digitalocean/netbox/issues/1057) - Corrected VLAN validation during prefix import
-* [#1061](https://github.com/digitalocean/netbox/issues/1061) - Fixed potential for script injection via create/edit/delete messages
-* [#1070](https://github.com/digitalocean/netbox/issues/1070) - Corrected installation instructions for Python3 on CentOS/RHEL
-* [#1071](https://github.com/digitalocean/netbox/issues/1071) - Protect assigned circuit termination when an interface is deleted
-* [#1072](https://github.com/digitalocean/netbox/issues/1072) - Order LAG interfaces naturally on bulk interface edit form
-* [#1074](https://github.com/digitalocean/netbox/issues/1074) - Require ncclient 0.5.3 (Python 3 fix)
-* [#1090](https://github.com/digitalocean/netbox/issues/1090) - Improved installation documentation for Python 3
-* [#1092](https://github.com/digitalocean/netbox/issues/1092) - Increase randomness in SECRET_KEY generation tool
-
----
-
-v1.9.5 (2017-04-06)
-
-## Improvements
-
-* [#1052](https://github.com/digitalocean/netbox/issues/1052) - Added rack reservation list and bulk delete views
-
-## Bug Fixes
-
-* [#1038](https://github.com/digitalocean/netbox/issues/1038) - Suppress upgrading to Django 1.11 (will be supported in v2.0)
-* [#1037](https://github.com/digitalocean/netbox/issues/1037) - Fixed error on VLAN import with duplicate VLAN group names
-* [#1047](https://github.com/digitalocean/netbox/issues/1047) - Correct ordering of numbered subinterfaces
-* [#1051](https://github.com/digitalocean/netbox/issues/1051) - Upgraded django-rest-swagger
-
----
-
-v1.9.4-r1 (2017-04-04)
-
-## Improvements
-
-* [#362](https://github.com/digitalocean/netbox/issues/362) - Added per_page query parameter to control pagination page length
-
-## Bug Fixes
-
-* [#991](https://github.com/digitalocean/netbox/issues/991) - Correct server error on "create and connect another" interface connection
-* [#1022](https://github.com/digitalocean/netbox/issues/1022) - Record user actions when creating IP addresses in bulk
-* [#1027](https://github.com/digitalocean/netbox/issues/1027) - Fixed nav menu highlighting when BASE_PATH is set
-* [#1034](https://github.com/digitalocean/netbox/issues/1034) - Added migration missing from v1.9.4 release
-
----
-
-v1.9.3 (2017-03-23)
-
-## Improvements
-
-* [#972](https://github.com/digitalocean/netbox/issues/972) - Add ability to filter connections list by device name
-* [#974](https://github.com/digitalocean/netbox/issues/974) - Added MAC address filter to API interfaces list
-* [#978](https://github.com/digitalocean/netbox/issues/978) - Allow filtering device types by function and subdevice role
-* [#981](https://github.com/digitalocean/netbox/issues/981) - Allow filtering primary objects by a given set of IDs
-* [#983](https://github.com/digitalocean/netbox/issues/983) - Include peer device names when listing circuits in device view
-
-## Bug Fixes
-
-* [#967](https://github.com/digitalocean/netbox/issues/967) - Fix error when assigning a new interface to a LAG
-
----
-
-v1.9.2 (2017-03-14)
-
-## Bug Fixes
-
-* [#950](https://github.com/digitalocean/netbox/issues/950) - Fix site_id error on child device import
-* [#956](https://github.com/digitalocean/netbox/issues/956) - Correct bug affecting unnamed rackless devices
-* [#957](https://github.com/digitalocean/netbox/issues/957) - Correct device site filter count to include unracked devices
-* [#963](https://github.com/digitalocean/netbox/issues/963) - Fix bug in IPv6 address range expansion
-* [#964](https://github.com/digitalocean/netbox/issues/964) - Fix bug when bulk editing/deleting filtered set of objects
-
----
-
-v1.9.1 (2017-03-08)
-
-## Improvements
-
-* [#945](https://github.com/digitalocean/netbox/issues/945) - Display the current user in the navigation menu
-* [#946](https://github.com/digitalocean/netbox/issues/946) - Disregard mask length when filtering IP addresses by a parent prefix
-
-## Bug Fixes
-
-* [#941](https://github.com/digitalocean/netbox/issues/941) - Corrected old references to rack.site on Device
-* [#943](https://github.com/digitalocean/netbox/issues/943) - Child prefixes missing on Python 3
-* [#944](https://github.com/digitalocean/netbox/issues/944) - Corrected console and power connection form behavior
-* [#948](https://github.com/digitalocean/netbox/issues/948) - Region name should be hyperlinked to site list
-
----
-
-v1.9.0-r1 (2017-03-03)
-
-## New Features
-
-### Rack Reservations ([#36](https://github.com/digitalocean/netbox/issues/36))
-
-Users can now reserve an arbitrary number of units within a rack, adding a comment noting their intentions. Reservations do not interfere with installed devices: It is possible to reserve a unit for future use even if it is currently occupied by a device.
-
-### Interface Groups ([#105](https://github.com/digitalocean/netbox/issues/105))
-
-A new Link Aggregation Group (LAG) virtual form factor has been added. Physical interfaces can be assigned to a parent LAG interface to represent a port-channel or similar logical bundling of links.
-
-### Regions ([#164](https://github.com/digitalocean/netbox/issues/164))
-
-A new region model has been introduced to allow for the geographic organization of sites. Regions can be nested recursively to form a hierarchy.
-
-### Rackless Devices ([#198](https://github.com/digitalocean/netbox/issues/198))
-
-Previous releases required each device to be assigned to a particular rack within a site. This requirement has been relaxed so that devices must only be assigned to a site, and may optionally be assigned to a rack.
-
-### Global VLANs ([#235](https://github.com/digitalocean/netbox/issues/235))
-
-Assignment of VLANs and VLAN groups to sites is now optional, allowing for the representation of a VLAN spanning multiple sites.
-
-## Improvements
-
-* [#862](https://github.com/digitalocean/netbox/issues/862) - Show both IPv6 and IPv4 primary IPs in device list
-* [#894](https://github.com/digitalocean/netbox/issues/894) - Expand device name max length to 64 characters
-* [#898](https://github.com/digitalocean/netbox/issues/898) - Expanded circuits list in provider view rack face
-* [#901](https://github.com/digitalocean/netbox/issues/901) - Support for filtering prefixes and IP addresses by mask length
-
-## Bug Fixes
-
-* [#872](https://github.com/digitalocean/netbox/issues/872) - Fixed TypeError on bulk IP address creation (Python 3)
-* [#884](https://github.com/digitalocean/netbox/issues/884) - Preserve selected rack unit when changing a device's rack face
-* [#892](https://github.com/digitalocean/netbox/issues/892) - Restored missing edit/delete buttons when viewing child prefixes and IP addresses from a parent object
-* [#897](https://github.com/digitalocean/netbox/issues/897) - Fixed power connections CSV export
-* [#903](https://github.com/digitalocean/netbox/issues/903) - Only alert on missing critical connections if present in the parent device type
-* [#935](https://github.com/digitalocean/netbox/issues/935) - Fix form validation error when connecting an interface using live search
-* [#937](https://github.com/digitalocean/netbox/issues/937) - Region assignment should be optional when creating a site
-* [#938](https://github.com/digitalocean/netbox/issues/938) - Provider view yields an error if one or more circuits is assigned to a tenant
-
----
-
-v1.8.4 (2017-02-03)
-
-## Improvements
-
-* [#856](https://github.com/digitalocean/netbox/issues/856) - Strip whitespace from fields during CSV import
-
-## Bug Fixes
-
-* [#851](https://github.com/digitalocean/netbox/issues/851) - Resolve encoding issues during import/export (Python 3)
-* [#854](https://github.com/digitalocean/netbox/issues/854) - Correct processing of get_return_url() in ObjectDeleteView
-* [#859](https://github.com/digitalocean/netbox/issues/859) - Fix Javascript for connection status toggle button on device view
-* [#861](https://github.com/digitalocean/netbox/issues/861) - Avoid overwriting device primary IP assignment from alternate family during bulk import of IP addresses
-* [#865](https://github.com/digitalocean/netbox/issues/865) - Fix server error when attempting to delete a protected object parent (Python 3)
-
----
-
-v1.8.3 (2017-01-26)
-
-## Improvements
-
-* [#782](https://github.com/digitalocean/netbox/issues/782) - Allow filtering devices list by manufacturer
-* [#820](https://github.com/digitalocean/netbox/issues/820) - Add VLAN column to parent prefixes table on IP address view
-* [#821](https://github.com/digitalocean/netbox/issues/821) - Support for comma separation in bulk IP/interface creation
-* [#827](https://github.com/digitalocean/netbox/issues/827) - **Introduced support for Python 3**
-* [#836](https://github.com/digitalocean/netbox/issues/836) - Add "deprecated" status for IP addresses
-* [#841](https://github.com/digitalocean/netbox/issues/841) - Merged search and filter forms on all object lists
-
-## Bug Fixes
-
-* [#816](https://github.com/digitalocean/netbox/issues/816) - Redirect back to parent prefix view after deleting child prefixes termination
-* [#817](https://github.com/digitalocean/netbox/issues/817) - Update last_updated time of a circuit when editing a child termination
-* [#830](https://github.com/digitalocean/netbox/issues/830) - Redirect user to device view after editing a device component
-* [#840](https://github.com/digitalocean/netbox/issues/840) - Correct API path resolution for secrets when BASE_PATH is configured
-* [#844](https://github.com/digitalocean/netbox/issues/844) - Apply order_naturally() to API interfaces list
-* [#845](https://github.com/digitalocean/netbox/issues/845) - Fix missing edit/delete buttons on object tables for non-superusers
-
-
----
-
-v1.8.2 (2017-01-18)
-
-## Improvements
-
-* [#284](https://github.com/digitalocean/netbox/issues/284) - Enabled toggling of interface display order per device type
-* [#760](https://github.com/digitalocean/netbox/issues/760) - Redirect user back to device view after deleting an assigned IP address
-* [#783](https://github.com/digitalocean/netbox/issues/783) - Add a description field to the Circuit model
-* [#797](https://github.com/digitalocean/netbox/issues/797) - Add description column to VLANs table
-* [#803](https://github.com/digitalocean/netbox/issues/803) - Clarify that no child objects are deleted when deleting a prefix
-* [#805](https://github.com/digitalocean/netbox/issues/805) - Linkify site column in device table
-
-## Bug Fixes
-
-* [#776](https://github.com/digitalocean/netbox/issues/776) - Prevent circuits from appearing twice while searching
-* [#778](https://github.com/digitalocean/netbox/issues/778) - Corrected an issue preventing multiple interfaces with the same position ID from appearing in a device's interface list
-* [#785](https://github.com/digitalocean/netbox/issues/785) - Trigger validation error when importing a prefix assigned to a nonexistent VLAN
-* [#802](https://github.com/digitalocean/netbox/issues/802) - Fixed enforcement of ENFORCE_GLOBAL_UNIQUE for prefixes
-* [#807](https://github.com/digitalocean/netbox/issues/807) - Redirect user back to form when adding IP addresses in bulk and "create and add another" is clicked
-* [#810](https://github.com/digitalocean/netbox/issues/810) - Suppress unique IP validation on invalid IP addresses and prefixes
-
----
-
-v1.8.1 (2017-01-04)
-
-## Improvements
-
-* [#771](https://github.com/digitalocean/netbox/issues/771) - Don't automatically redirect user when only one object is returned in a list
-
-## Bug Fixes
-
-* [#764](https://github.com/digitalocean/netbox/issues/764) - Encapsulate in double quotes values containing commas when exporting to CSV
-* [#767](https://github.com/digitalocean/netbox/issues/767) - Fixes xconnect_id error when searching for circuits
-* [#769](https://github.com/digitalocean/netbox/issues/769) - Show default value for boolean custom fields
-* [#772](https://github.com/digitalocean/netbox/issues/772) - Fixes TypeError in API RackUnitListView when no device is excluded
-
----
-
-v1.8.0 (2017-01-03)
-
-## New Features
-
-### Point-to-Point Circuits ([#49](https://github.com/digitalocean/netbox/issues/49))
-
-Until now, NetBox has supported tracking only one end of a data circuit. This is fine for Internet connections where you don't care (or know) much about the provider side of the circuit, but many users need the ability to track inter-site circuits as well. This release expands circuit modeling so that each circuit can have an A and/or Z side. Each endpoint must be terminated to a site, and may optionally be terminated to a specific device and interface within that site.
-
-### L4 Services ([#539](https://github.com/digitalocean/netbox/issues/539))
-
-Our first major community contribution introduces the ability to track discrete TCP and UDP services associated with a device (for example, SSH or HTTP). Each service can optionally be assigned to one or more specific IP addresses belonging to the device. Thanks to [@if-fi](https://github.com/if-fi) for the addition!
-
-## Improvements
-
-* [#122](https://github.com/digitalocean/netbox/issues/122) - Added comments field to device types
-* [#181](https://github.com/digitalocean/netbox/issues/181) - Implemented support for bulk IP address creation
-* [#613](https://github.com/digitalocean/netbox/issues/613) - Added prefixes column to VLAN list; added VLAN column to prefix list
-* [#716](https://github.com/digitalocean/netbox/issues/716) - Add ASN field to site bulk edit form
-* [#722](https://github.com/digitalocean/netbox/issues/722) - Enabled custom fields for device types
-* [#743](https://github.com/digitalocean/netbox/issues/743) - Enabled bulk creation of all device components
-* [#756](https://github.com/digitalocean/netbox/issues/756) - Added contact details to site model
-
-## Bug Fixes
-
-* [#563](https://github.com/digitalocean/netbox/issues/563) - Allow a device to be flipped from one rack face to the other without moving it
-* [#658](https://github.com/digitalocean/netbox/issues/658) - Enabled conditional treatment of network/broadcast IPs for a prefix by defining it as a pool
-* [#741](https://github.com/digitalocean/netbox/issues/741) - Hide "select all" button for users without edit permissions
-* [#744](https://github.com/digitalocean/netbox/issues/744) - Fixed export of sites without an AS number
-* [#747](https://github.com/digitalocean/netbox/issues/747) - Fixed natural_order_by integer cast error on large numbers
-* [#751](https://github.com/digitalocean/netbox/issues/751) - Fixed python-cryptography installation issue on Debian
-* [#763](https://github.com/digitalocean/netbox/issues/763) - Added missing fields to CSV exports for racks and prefixes
-
----
-
-v1.7.3 (2016-12-08)
-
-## Bug Fixes
-
-* [#724](https://github.com/digitalocean/netbox/issues/724) - Exempt API views from LoginRequiredMiddleware to enable basic HTTP authentication when LOGIN_REQUIRED is true
-* [#729](https://github.com/digitalocean/netbox/issues/729) - Corrected cancellation links when editing secondary objects
-* [#732](https://github.com/digitalocean/netbox/issues/732) - Allow custom select field values to be deselected if the field is not required
-* [#733](https://github.com/digitalocean/netbox/issues/733) - Fixed MAC address filter on device list
-* [#734](https://github.com/digitalocean/netbox/issues/734) - Corrected display of device type when editing a device
-
----
-
-v1.7.2-r1 (2016-12-06)
-
-## Improvements
-
-* [#663](https://github.com/digitalocean/netbox/issues/663) - Added MAC address search field to device list
-* [#672](https://github.com/digitalocean/netbox/issues/672) - Increased the selection of available colors for rack and device roles
-* [#695](https://github.com/digitalocean/netbox/issues/695) - Added is_private field to RIR
-
-## Bug Fixes
-
-* [#677](https://github.com/digitalocean/netbox/issues/677) - Fix setuptools installation error on Debian 8.6
-* [#696](https://github.com/digitalocean/netbox/issues/696) - Corrected link to VRF in prefix and IP address breadcrumbs
-* [#702](https://github.com/digitalocean/netbox/issues/702) - Improved Unicode support for custom fields
-* [#712](https://github.com/digitalocean/netbox/issues/712) - Corrected export of tenants which are not assigned to a group
-* [#713](https://github.com/digitalocean/netbox/issues/713) - Include a label for the comments field when editing circuits, providers, or racks in bulk
-* [#718](https://github.com/digitalocean/netbox/issues/718) - Restore is_primary field on IP assignment form
-* [#723](https://github.com/digitalocean/netbox/issues/723) - API documentation is now accessible when using BASE_PATH
-* [#727](https://github.com/digitalocean/netbox/issues/727) - Corrected error in rack elevation display (v1.7.2)
-
----
-
-v1.7.1 (2016-11-15)
-
-## Improvements
-
-* [#667](https://github.com/digitalocean/netbox/issues/667) - Added prefix utilization statistics to the RIR list view
-* [#685](https://github.com/digitalocean/netbox/issues/685) - When assigning an IP to a device, automatically select the interface if only one exists
-
-## Bug Fixes
-
-* [#674](https://github.com/digitalocean/netbox/issues/674) - Fix assignment of status to imported IP addresses
-* [#676](https://github.com/digitalocean/netbox/issues/676) - Server error when bulk editing device types
-* [#678](https://github.com/digitalocean/netbox/issues/678) - Server error on device import specifying an invalid device type
-* [#691](https://github.com/digitalocean/netbox/issues/691) - Allow the assignment of power ports to PDUs
-* [#692](https://github.com/digitalocean/netbox/issues/692) - Form errors are not displayed on checkbox fields
-
----
-
-v1.7.0 (2016-11-03)
-
-## New Features
-
-### IP address statuses ([#87](https://github.com/digitalocean/netbox/issues/87))
-
-An IP address can now be designated as active, reserved, or DHCP. The DHCP status implies that the IP address is part of a DHCP pool and may or may not be assigned to a DHCP client.
-
-### Top-to-bottom rack numbering ([#191](https://github.com/digitalocean/netbox/issues/191))
-
-Racks can now be set to have descending rack units, with U1 at the top of the rack. When adding a device to a rack with descending units, be sure to position it in the **lowest-numbered** unit which it occupies (this will be physically the topmost unit).
-
-## Improvements
-* [#211](https://github.com/digitalocean/netbox/issues/211) - Allow device assignment and removal from IP address view
-* [#630](https://github.com/digitalocean/netbox/issues/630) - Added a custom 404 page
-* [#652](https://github.com/digitalocean/netbox/issues/652) - Use password input controls when editing secrets
-* [#654](https://github.com/digitalocean/netbox/issues/654) - Added Cisco FlexStack and FlexStack Plus form factors
-* [#661](https://github.com/digitalocean/netbox/issues/661) - Display relevant IP addressing when viewing a circuit
-
-## Bug Fixes
-* [#632](https://github.com/digitalocean/netbox/issues/632) - Use semicolons instead of commas to separate regexes in topology maps
-* [#647](https://github.com/digitalocean/netbox/issues/647) - Extend form used when assigning an IP to a device
-* [#657](https://github.com/digitalocean/netbox/issues/657) - Unicode error when adding device modules
-* [#660](https://github.com/digitalocean/netbox/issues/660) - Corrected calculation of utilized space in rack list
-* [#664](https://github.com/digitalocean/netbox/issues/664) - Fixed bulk creation of interfaces across multiple devices
-
----
-
-v1.6.3 (2016-10-19)
-
-## Improvements
-
-* [#353](https://github.com/digitalocean/netbox/issues/353) - Bulk editing of device and device type interfaces
-* [#527](https://github.com/digitalocean/netbox/issues/527) - Support for nullification of fields when bulk editing
-* [#592](https://github.com/digitalocean/netbox/issues/592) - Allow space-delimited lists of ALLOWED_HOSTS in Docker
-* [#608](https://github.com/digitalocean/netbox/issues/608) - Added "select all" button for device and device type components
-
-## Bug Fixes
-
-* [#602](https://github.com/digitalocean/netbox/issues/602) - Correct display of custom integer fields with value of 0 or 1
-* [#604](https://github.com/digitalocean/netbox/issues/604) - Correct display of unnamed devices in form selection fields
-* [#611](https://github.com/digitalocean/netbox/issues/611) - Power/console/interface connection import: status field should be case-insensitive
-* [#615](https://github.com/digitalocean/netbox/issues/615) - Account for BASE_PATH in static URLs and during login
-* [#616](https://github.com/digitalocean/netbox/issues/616) - Correct display of custom URL fields
-
----
-
-v1.6.2-r1 (2016-10-04)
-
-## Improvements
-
-* [#212](https://github.com/digitalocean/netbox/issues/212) - Introduced the `BASE_PATH` configuration setting to allow running NetBox in a URL subdirectory
-* [#345](https://github.com/digitalocean/netbox/issues/345) - Bulk edit: allow user to select all objects on page or all matching query
-* [#475](https://github.com/digitalocean/netbox/issues/475) - Display "add" buttons at top and bottom of all device/device type panels
-* [#480](https://github.com/digitalocean/netbox/issues/480) - Improved layout on mobile devices
-* [#481](https://github.com/digitalocean/netbox/issues/481) - Require interface creation before trying to assign an IP to a device
-* [#575](https://github.com/digitalocean/netbox/issues/575) - Allow all valid URL schemes in custom fields
-* [#579](https://github.com/digitalocean/netbox/issues/579) - Add a description field to export templates
-
-## Bug Fixes
-
-* [#466](https://github.com/digitalocean/netbox/issues/466) - Validate available free space for all instances when increasing the U height of a device type
-* [#571](https://github.com/digitalocean/netbox/issues/571) - Correct rack group filter on device list
-* [#576](https://github.com/digitalocean/netbox/issues/576) - Delete all relevant CustomFieldValues when deleting a CustomFieldChoice
-* [#581](https://github.com/digitalocean/netbox/issues/581) - Correct initialization of custom boolean and select fields
-* [#591](https://github.com/digitalocean/netbox/issues/591) - Correct display of component creation buttons in device type view
-
----
-
-v1.6.1-r1 (2016-09-21)
-
-## Improvements
-* [#415](https://github.com/digitalocean/netbox/issues/415) - Add an expand/collapse toggle button to the prefix list
-* [#552](https://github.com/digitalocean/netbox/issues/552) - Allow filtering on custom select fields by "none"
-* [#561](https://github.com/digitalocean/netbox/issues/561) - Make custom fields accessible from within export templates
-
-## Bug Fixes
-* [#493](https://github.com/digitalocean/netbox/issues/493) - CSV import support for UTF-8
-* [#531](https://github.com/digitalocean/netbox/issues/531) - Order prefix list by VRF assignment
-* [#542](https://github.com/digitalocean/netbox/issues/542) - Add LDAP support in Docker
-* [#557](https://github.com/digitalocean/netbox/issues/557) - Add 'global' choice to VRF filter for prefixes and IP addresses
-* [#558](https://github.com/digitalocean/netbox/issues/558) - Update slug field when name is populated without a key press
-* [#562](https://github.com/digitalocean/netbox/issues/562) - Fixed bulk interface creation
-* [#564](https://github.com/digitalocean/netbox/issues/564) - Display custom fields for all applicable objects
-
----
-
-v1.6.0 (2016-09-13)
-
-## New Features
-
-### Custom Fields ([#129](https://github.com/digitalocean/netbox/issues/129))
-
-Users can now create custom fields to associate arbitrary data with core NetBox objects. For example, you might want to add a geolocation tag to IP prefixes, or a ticket number to each device. Text, integer, boolean, date, URL, and selection fields are supported.
-
-## Improvements
-
-* [#489](https://github.com/digitalocean/netbox/issues/489) - Docker file now builds from a `python:2.7-wheezy` base instead of `ubuntu:14.04`
-* [#540](https://github.com/digitalocean/netbox/issues/540) - Add links for VLAN roles under VLAN navigation menu
-* Added new interface form factors
-* Added address family filters to aggregate and prefix lists
-
-## Bug Fixes
-
-* [#476](https://github.com/digitalocean/netbox/issues/476) - Corrected rack import instructions
-* [#484](https://github.com/digitalocean/netbox/issues/484) - Allow bulk deletion of >1K objects
-* [#486](https://github.com/digitalocean/netbox/issues/486) - Prompt for secret key only if updating a secret's value
-* [#490](https://github.com/digitalocean/netbox/issues/490) - Corrected display of circuit commit rate
-* [#495](https://github.com/digitalocean/netbox/issues/495) - Include tenant in prefix and IP CSV export
-* [#507](https://github.com/digitalocean/netbox/issues/507) - Corrected rendering of nav menu on screens narrower than 1200px
-* [#515](https://github.com/digitalocean/netbox/issues/515) - Clarified instructions for the "face" field when importing devices
-* [#522](https://github.com/digitalocean/netbox/issues/522) - Remove obsolete check for staff status when bulk deleting objects
-* [#544](https://github.com/digitalocean/netbox/issues/544) - Strip CRLF-style line terminators from rendered export templates
-
----
-
-v1.5.2 (2016-08-16)
-
-## Bug Fixes
-
-* [#460](https://github.com/digitalocean/netbox/issues/460) - Corrected ordering of IP addresses with differing prefix lengths
-* [#463](https://github.com/digitalocean/netbox/issues/463) - Prevent pre-population of livesearch field with '---------'
-* [#467](https://github.com/digitalocean/netbox/issues/467) - Include prefixes and IPs which inherit tenancy from their VRF in tenant stats
-* [#468](https://github.com/digitalocean/netbox/issues/468) - Don't allow connected interfaces to be changed to the "virtual" form factor
-* [#469](https://github.com/digitalocean/netbox/issues/469) - Added missing import buttons to list views
-* [#472](https://github.com/digitalocean/netbox/issues/472) - Hide the connection button for interfaces which have a circuit terminated to them
-
----
-
-v1.5.1 (2016-08-11)
-
-## Improvements
-
-* [#421](https://github.com/digitalocean/netbox/issues/421) - Added an asset tag field to devices
-* [#456](https://github.com/digitalocean/netbox/issues/456) - Added IP search box to home page
-* Colorized rack and device roles
-
-## Bug Fixes
-
-* [#454](https://github.com/digitalocean/netbox/issues/454) - Corrected error on rack export
-* [#457](https://github.com/digitalocean/netbox/issues/457) - Added role field to rack edit form
-
----
-
-v1.5.0 (2016-08-10)
-
-## New Features
-
-### Rack Enhancements ([#180](https://github.com/digitalocean/netbox/issues/180), [#241](https://github.com/digitalocean/netbox/issues/241))
-
-Like devices, racks can now be assigned to functional roles. This allows users to group racks by designated function as well as by physical location (rack groups). Additionally, rack can now have a defined rail-to-rail width (19 or 23 inches) and a type (two-post-rack, cabinet, etc.).
-
-## Improvements
-
-* [#149](https://github.com/digitalocean/netbox/issues/149) - Added discrete upstream speed field for circuits
-* [#157](https://github.com/digitalocean/netbox/issues/157) - Added manufacturer field for device modules
-* We have a logo!
-* Upgraded to Django 1.10
-
-## Bug Fixes
-
-* [#433](https://github.com/digitalocean/netbox/issues/433) - Corrected form validation when editing child devices
-* [#442](https://github.com/digitalocean/netbox/issues/442) - Corrected child device import instructions
-* [#443](https://github.com/digitalocean/netbox/issues/443) - Correctly display and initialize VRF for creation of new IP addresses
-* [#444](https://github.com/digitalocean/netbox/issues/444) - Corrected prefix model validation
-* [#445](https://github.com/digitalocean/netbox/issues/445) - Limit rack height to between 1U and 100U (inclusive)
-
----
-
-v1.4.2 (2016-08-06)
-
-## Improvements
-
-* [#167](https://github.com/digitalocean/netbox/issues/167) - Added new interface form factors
-* [#253](https://github.com/digitalocean/netbox/issues/253) - Added new interface form factors
-* [#434](https://github.com/digitalocean/netbox/issues/434) - Restored admin UI access to user action history (however bulk deletion is disabled)
-* [#435](https://github.com/digitalocean/netbox/issues/435) - Added an "add prefix" button to the VLAN view
-
-## Bug Fixes
-
-* [#425](https://github.com/digitalocean/netbox/issues/425) - Ignore leading and trailing periods when generating a slug
-* [#427](https://github.com/digitalocean/netbox/issues/427) - Prevent error when duplicate IPs are present in a prefix's IP list
-* [#429](https://github.com/digitalocean/netbox/issues/429) - Correct redirection of user when adding a secret to a device
-
----
-
-v1.4.1 (2016-08-03)
-
-## Improvements
-
-* [#289](https://github.com/digitalocean/netbox/issues/289) - Annotate available ranges in prefix IP list
-* [#412](https://github.com/digitalocean/netbox/issues/412) - Tenant group assignment is no longer mandatory
-* [#422](https://github.com/digitalocean/netbox/issues/422) - CSV import now supports double-quoting values which contain commas
-
-## Bug Fixes
-
-* [#395](https://github.com/digitalocean/netbox/issues/395) - Show child prefixes from all VRFs if the parent belongs to the global table
-* [#406](https://github.com/digitalocean/netbox/issues/406) - Fixed circuit list rendring when filtering on port speed or commit rate
-* [#409](https://github.com/digitalocean/netbox/issues/409) - Filter IPs and prefixes by tenant slug rather than by its PK
-* [#411](https://github.com/digitalocean/netbox/issues/411) - Corrected title of secret roles view
-* [#419](https://github.com/digitalocean/netbox/issues/419) - Fixed a potential database performance issue when gathering tenant statistics
-
----
-
-v1.4.0 (2016-08-01)
-
-## New Features
-
-### Multitenancy ([#16](https://github.com/digitalocean/netbox/issues/16))
-
-NetBox now supports tenants and tenant groups. Sites, racks, devices, VRFs, prefixes, IP addresses, VLANs, and circuits can be assigned to tenants to track the allocation of these resources among customers or internal departments. If a prefix or IP address does not have a tenant assigned, it will fall back to the tenant assigned to its parent VRF (where applicable).
-
-## Improvements
-
-* [#176](https://github.com/digitalocean/netbox/issues/176) - Introduced seed data for new installs
-* [#358](https://github.com/digitalocean/netbox/issues/358) - Improved search for all objects
-* [#394](https://github.com/digitalocean/netbox/issues/394) - Improved VRF selection during bulk editing of prefixes and IP addresses
-* Miscellaneous cosmetic improvements to the UI
-
-## Bug Fixes
-
-* [#392](https://github.com/digitalocean/netbox/issues/392) - Don't include child devices in non-racked devices table
-* [#397](https://github.com/digitalocean/netbox/issues/397) - Only include child IPs which belong to the same VRF as the parent prefix
-
----
-
-v1.3.2 (2016-07-26)
-
-## Improvements
-
-* [#292](https://github.com/digitalocean/netbox/issues/292) - Added part_number field to DeviceType
-* [#363](https://github.com/digitalocean/netbox/issues/363) - Added a description field to the VLAN model
-* [#374](https://github.com/digitalocean/netbox/issues/374) - Increased VLAN name length to 64 characters
-* Enabled bulk deletion of interfaces from devices
-
-## Bug Fixes
-
-* [#359](https://github.com/digitalocean/netbox/issues/359) - Corrected the DCIM API endpoint for finding related connections
-* [#370](https://github.com/digitalocean/netbox/issues/370) - Notify user when secret decryption fails
-* [#381](https://github.com/digitalocean/netbox/issues/381) - Fix 'u_consumed' error on rack import
-* [#384](https://github.com/digitalocean/netbox/issues/384) - Fixed description field's maximum length on IPAM bulk edit forms
-* [#385](https://github.com/digitalocean/netbox/issues/385) - Fixed error when deleting a user with one or more associated UserActions
-
----
-
-v1.3.1 (2016-07-21)
-
-## Improvements
-
-* [#258](https://github.com/digitalocean/netbox/issues/258) - Add an API endpoint to list interface connections
-* [#303](https://github.com/digitalocean/netbox/issues/303) - Improved numeric ordering of sites, racks, and devices
-* [#304](https://github.com/digitalocean/netbox/issues/304) - Display utilization percentage on rack list
-* [#327](https://github.com/digitalocean/netbox/issues/327) - Disable rack assignment for installed child devices
-
-## Bug Fixes
-
-* [#331](https://github.com/digitalocean/netbox/issues/331) - Add group field to VLAN bulk edit form
-* Miscellaneous improvements to Unicode handling
-
----
-
-v1.3.0 (2016-07-18)
-
-## New Features
-
-* [#42](https://github.com/digitalocean/netbox/issues/42) - Allow assignment of VLAN on prefix import
-* [#43](https://github.com/digitalocean/netbox/issues/43) - Toggling of IP space uniqueness within a VRF
-* [#111](https://github.com/digitalocean/netbox/issues/111) - Introduces VLAN groups
-* [#227](https://github.com/digitalocean/netbox/issues/227) - Support for bulk import of child devices
-
-## Bug Fixes
-
-* [#301](https://github.com/digitalocean/netbox/issues/301) - Prevent deletion of DeviceBay when installed device is deleted
-* [#306](https://github.com/digitalocean/netbox/issues/306) - Fixed device import to allow an unspecified rack face
-* [#307](https://github.com/digitalocean/netbox/issues/307) - Catch `RelatedObjectDoesNotExist` when an invalid device type is defined during device import
-* [#308](https://github.com/digitalocean/netbox/issues/308) - Update rack assignment for all child devices when moving a parent device
-* [#311](https://github.com/digitalocean/netbox/issues/311) - Fix assignment of primary_ip on IP address import
-* [#317](https://github.com/digitalocean/netbox/issues/317) - Rack elevation display fix for device types greater than 42U in height
-* [#320](https://github.com/digitalocean/netbox/issues/320) - Disallow import of prefixes with host masks
-* [#322](https://github.com/digitalocean/netbox/issues/320) - Corrected VLAN import behavior
-
----
-
-v1.2.2 (2016-07-14)
-
-## Improvements
-
-* [#174](https://github.com/digitalocean/netbox/issues/174) - Added search and site filter to provider list
-* [#270](https://github.com/digitalocean/netbox/issues/270) - Added the ability to filter devices by rack group
-
-## Bug Fixes
-
-* [#115](https://github.com/digitalocean/netbox/issues/115) - Fix deprecated django.core.context_processors reference
-* [#268](https://github.com/digitalocean/netbox/issues/268) - Added support for entire 32-bit ASN space
-* [#282](https://github.com/digitalocean/netbox/issues/282) - De-select "all" checkbox if one or more objects are deselected
-* [#290](https://github.com/digitalocean/netbox/issues/290) - Always display management interfaces for a device type (even if `is_network_device` is not set)
-
----
-
-v1.2.1 (2016-07-13)
-
-**Note:** This release introduces a new dependency ([natsort](https://pypi.python.org/pypi/natsort)). Be sure to run `upgrade.sh` if upgrading from a previous release.
-
-## Improvements
-
-* [#285](https://github.com/digitalocean/netbox/issues/285) - Added the ability to prefer IPv4 over IPv6 for primary device IPs
-
-## Bug Fixes
-
-* [#243](https://github.com/digitalocean/netbox/issues/243) - Improved ordering of device object lists
-* [#271](https://github.com/digitalocean/netbox/issues/271) - Fixed primary_ip bug in secrets API
-* [#274](https://github.com/digitalocean/netbox/issues/274) - Fixed primary_ip bug in DCIM admin UI
-* [#275](https://github.com/digitalocean/netbox/issues/275) - Fixed bug preventing the expansion of an existing aggregate
-
----
-
-v1.2.0 (2016-07-12)
-
-## New Features
-
-* [#73](https://github.com/digitalocean/netbox/issues/73) - Added optional persistent banner
-* [#93](https://github.com/digitalocean/netbox/issues/73) - Ability to set both IPv4 and IPv6 primary IPs for devices
-* [#203](https://github.com/digitalocean/netbox/issues/203) - Introduced support for LDAP
-
-## Bug Fixes
-
-* [#162](https://github.com/digitalocean/netbox/issues/228) - Fixed support for Unicode characters in rack/device/VLAN names
-* [#228](https://github.com/digitalocean/netbox/issues/228) - Corrected conditional inclusion of device bay templates
-* [#246](https://github.com/digitalocean/netbox/issues/246) - Corrected Docker build instructions
-* [#260](https://github.com/digitalocean/netbox/issues/260) - Fixed error on admin UI device type list
-* Miscellaneous layout improvements for mobile devices
-
----
-
-v1.1.0 (2016-07-07)
-
-## New Features
-
-* [#107](https://github.com/digitalocean/netbox/pull/107) - Docker support
-* [#91](https://github.com/digitalocean/netbox/issues/91) - Support for subdevices within a device
-* [#170](https://github.com/digitalocean/netbox/pull/170) - Added MAC address field to interfaces
-
-## Bug Fixes
-
-* [#169](https://github.com/digitalocean/netbox/issues/169) - Fix rendering of cancellation URL when editing objects
-* [#183](https://github.com/digitalocean/netbox/issues/183) - Ignore vi swap files
-* [#209](https://github.com/digitalocean/netbox/issues/209) - Corrected error when not confirming component template deletions
-* [#214](https://github.com/digitalocean/netbox/issues/214) - Fixed redundant message on bulk interface creation
-* [#68](https://github.com/digitalocean/netbox/issues/68) - Improved permissions-related error reporting for secrets
-
----
-
-v1.0.7-r1 (2016-07-05)
-
-* [#199](https://github.com/digitalocean/netbox/issues/199) - Correct IP address validation
-
----
-
-v1.0.7 (2016-06-30)
-
-**Note:** If upgrading from a previous release, be sure to run ./upgrade.sh after downloading the new code.
-* [#135](https://github.com/digitalocean/netbox/issues/135): Fixed display of navigation menu on mobile screens
-* [#141](https://github.com/digitalocean/netbox/issues/141): Fixed rendering of "getting started" guide
-* Modified upgrade.sh to use sudo for pip installations
-* [#109](https://github.com/digitalocean/netbox/issues/109): Hide the navigation menu from anonymous users if login is required
-* [#143](https://github.com/digitalocean/netbox/issues/143): Add help_text to Device.position
-* [#136](https://github.com/digitalocean/netbox/issues/136): Prefixes which have host bits set will trigger an error instead of being silently corrected
-* [#140](https://github.com/digitalocean/netbox/issues/140): Improved support for Unicode in object names
-
----
-
-1.0.0 (2016-06-27)
-
-NetBox was originally developed internally at DigitalOcean by the network development team. This release marks the debut of NetBox as an open source project.
+Select2 issues
+* [#2516](https://github.com/digitalocean/netbox/issues/2516) - Implemented Select2 for all Model backed selection fields
+* [#2590](https://github.com/digitalocean/netbox/issues/2590) - Implemented the color picker with Select2 to show colors in the background
+* [#2735](https://github.com/digitalocean/netbox/issues/2735) - Implemented Select2 for all list filter form select elements
+* [#2753](https://github.com/digitalocean/netbox/issues/2753) - Implemented Select2 to replace most all instances of select fields in forms
+
+
+v2.5.4 (FUTURE)
+
+## Bug Fixes
+
+* [#2779](https://github.com/digitalocean/netbox/issues/2779) - Include "none" option when filter IP addresses by role
+* [#2783](https://github.com/digitalocean/netbox/issues/2783) - Fix AttributeError exception when attempting to delete region(s)
+
+---
+
+v2.5.3 (2019-01-11)
+
+## Enhancements
+
+* [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length
+* [#1870](https://github.com/digitalocean/netbox/issues/1870) - Add per-page toggle to object lists
+* [#1871](https://github.com/digitalocean/netbox/issues/1871) - Enable filtering sites by parent region
+* [#1983](https://github.com/digitalocean/netbox/issues/1983) - Enable regular expressions when bulk renaming device components
+* [#2682](https://github.com/digitalocean/netbox/issues/2682) - Add DAC and AOC cable types
+* [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors
+* [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search
+
+## Bug Fixes
+
+* [#2742](https://github.com/digitalocean/netbox/issues/2742) - Preserve cluster assignment when editing a device
+* [#2757](https://github.com/digitalocean/netbox/issues/2757) - Always treat first/last IPs within a /31 or /127 as usable
+* [#2762](https://github.com/digitalocean/netbox/issues/2762) - Add missing DCIM field values to API `_choices` endpoint
+* [#2777](https://github.com/digitalocean/netbox/issues/2777) - Fix cable validation to handle duplicate connections on import
+
+
+---
+
+v2.5.2 (2018-12-21)
+
+## Enhancements
+
+* [#2561](https://github.com/digitalocean/netbox/issues/2561) - Add 200G and 400G interface types
+* [#2701](https://github.com/digitalocean/netbox/issues/2701) - Enable filtering of prefixes by exact prefix value
+
+## Bug Fixes
+
+* [#2673](https://github.com/digitalocean/netbox/issues/2673) - Fix exception on LLDP neighbors view for device with a circuit connected
+* [#2691](https://github.com/digitalocean/netbox/issues/2691) - Cable trace should follow circuits
+* [#2698](https://github.com/digitalocean/netbox/issues/2698) - Remove pagination restriction on bulk component creation for devices/VMs
+* [#2704](https://github.com/digitalocean/netbox/issues/2704) - Fix form select widget population on parent with null value
+* [#2707](https://github.com/digitalocean/netbox/issues/2707) - Correct permission evaluation for circuit termination cabling
+* [#2712](https://github.com/digitalocean/netbox/issues/2712) - Preserve list filtering after editing objects in bulk
+* [#2717](https://github.com/digitalocean/netbox/issues/2717) - Fix bulk deletion of tags
+* [#2721](https://github.com/digitalocean/netbox/issues/2721) - Detect loops when tracing front/rear ports
+* [#2723](https://github.com/digitalocean/netbox/issues/2723) - Correct permission evaluation when bulk deleting tags
+* [#2724](https://github.com/digitalocean/netbox/issues/2724) - Limit rear port choices to current device when editing a front port
+
+---
+
+v2.5.1 (2018-12-13)
+
+## Enhancements
+
+* [#2655](https://github.com/digitalocean/netbox/issues/2655) - Add 128GFC Fibrechannel interface type
+* [#2674](https://github.com/digitalocean/netbox/issues/2674) - Enable filtering changelog by object type under web UI
+
+## Bug Fixes
+
+* [#2662](https://github.com/digitalocean/netbox/issues/2662) - Fix ImproperlyConfigured exception when rendering API docs
+* [#2663](https://github.com/digitalocean/netbox/issues/2663) - Prevent duplicate interfaces from appearing under VLAN members view
+* [#2666](https://github.com/digitalocean/netbox/issues/2666) - Correct display of length unit in cables list
+* [#2676](https://github.com/digitalocean/netbox/issues/2676) - Fix exception when passing dictionary value to a ChoiceField
+* [#2678](https://github.com/digitalocean/netbox/issues/2678) - Fix error when viewing webhook in admin UI without write permission
+* [#2680](https://github.com/digitalocean/netbox/issues/2680) - Disallow POST requests to `/dcim/interface-connections/` API endpoint
+* [#2683](https://github.com/digitalocean/netbox/issues/2683) - Fix exception when connecting a cable to a RearPort with no corresponding FrontPort
+* [#2684](https://github.com/digitalocean/netbox/issues/2684) - Fix custom field filtering
+* [#2687](https://github.com/digitalocean/netbox/issues/2687) - Correct naming of before/after filters for changelog entries
+
+---
+
+v2.5.0 (2018-12-10)
+
+## Notes
+
+### Python 3 Required
+
+As promised, Python 2 support has been completed removed. Python 3.5 or higher is now required to run NetBox. Please see [our Python 3 migration guide](https://netbox.readthedocs.io/en/stable/installation/migrating-to-python3/) for assistance with upgrading.
+
+### Removed Deprecated User Activity Log
+
+The UserAction model, which was deprecated by the new change logging feature in NetBox v2.4, has been removed. If you need to archive legacy user activity, do so prior to upgrading to NetBox v2.5, as the database migration will remove all data associated with this model.
+
+### View Permissions in Django 2.1
+
+Django 2.1 introduces view permissions for object types (not to be confused with object-level permissions). Implementation of [#323](https://github.com/digitalocean/netbox/issues/323) is planned for NetBox v2.6. Users are encourage to begin assigning view permissions as desired in preparation for their eventual enforcement.
+
+### upgrade.sh No Longer Invokes sudo
+
+The `upgrade.sh` script has been tweaked so that it no longer invokes `sudo` internally. This was done to ensure compatibility when running NetBox inside a Python virtual environment. If you need elevated permissions when upgrading NetBox, call the upgrade script with `sudo upgrade.sh`.
+
+## New Features
+
+### Patch Panels and Cables ([#20](https://github.com/digitalocean/netbox/issues/20))
+
+NetBox now supports modeling physical cables for console, power, and interface connections. The new pass-through port component type has also been introduced to model patch panels and similar devices.
+
+## Enhancements
+
+* [#450](https://github.com/digitalocean/netbox/issues/450) - Added `outer_width` and `outer_depth` fields to rack model
+* [#867](https://github.com/digitalocean/netbox/issues/867) - Added `description` field to circuit terminations
+* [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added an `asset_tag` field for racks
+* [#1931](https://github.com/digitalocean/netbox/issues/1931) - Added a count of assigned IP addresses to the interface API serializer
+* [#2000](https://github.com/digitalocean/netbox/issues/2000) - Dropped support for Python 2
+* [#2053](https://github.com/digitalocean/netbox/issues/2053) - Introduced the `LOGIN_TIMEOUT` configuration setting
+* [#2057](https://github.com/digitalocean/netbox/issues/2057) - Added description columns to interface connections list
+* [#2104](https://github.com/digitalocean/netbox/issues/2104) - Added a `status` field for racks
+* [#2165](https://github.com/digitalocean/netbox/issues/2165) - Improved natural ordering of Interfaces
+* [#2292](https://github.com/digitalocean/netbox/issues/2292) - Removed the deprecated UserAction model
+* [#2367](https://github.com/digitalocean/netbox/issues/2367) - Removed deprecated RPCClient functionality
+* [#2426](https://github.com/digitalocean/netbox/issues/2426) - Introduced `SESSION_FILE_PATH` configuration setting for authentication without write access to database
+* [#2594](https://github.com/digitalocean/netbox/issues/2594) - `upgrade.sh` no longer invokes sudo
+
+## Changes From v2.5-beta2
+
+* [#2474](https://github.com/digitalocean/netbox/issues/2474) - Add `cabled` and `connection_status` filters for device components
+* [#2616](https://github.com/digitalocean/netbox/issues/2616) - Convert Rack `outer_unit` and Cable `length_unit` to integer-based choice fields
+* [#2622](https://github.com/digitalocean/netbox/issues/2622) - Enable filtering cables by multiple types/colors
+* [#2624](https://github.com/digitalocean/netbox/issues/2624) - Delete associated content type and permissions when removing InterfaceConnection model
+* [#2626](https://github.com/digitalocean/netbox/issues/2626) - Remove extraneous permissions generated from proxy models
+* [#2632](https://github.com/digitalocean/netbox/issues/2632) - Change representation of null values from `0` to `null`
+* [#2639](https://github.com/digitalocean/netbox/issues/2639) - Fix preservation of length/dimensions unit for racks and cables
+* [#2648](https://github.com/digitalocean/netbox/issues/2648) - Include the `connection_status` field in nested represenations of connectable device components
+* [#2649](https://github.com/digitalocean/netbox/issues/2649) - Add `connected_endpoint_type` to connectable device component API representations
+
+## API Changes
+
+* The `/extras/recent-activity/` endpoint (replaced by change logging in v2.4) has been removed
+* The `rpc_client` field has been removed from dcim.Platform (see #2367)
+* Introduced a new API endpoint for cables at `/dcim/cables/`
+* New endpoints for front and rear pass-through ports (and their templates) in parallel with existing device components
+* The fields `interface_connection` on Interface and `interface` on CircuitTermination have been replaced with `connected_endpoint` and `connection_status`
+* A new `cable` field has been added to console, power, and interface components and to circuit terminations
+* New fields for dcim.Rack: `status`, `asset_tag`, `outer_width`, `outer_depth`, `outer_unit`
+* The following boolean filters on dcim.Device and dcim.DeviceType have been renamed:
+    * `is_console_server`: `console_server_ports`
+    * `is_pdu`: `power_outlets`
+    * `is_network_device`: `interfaces`
+* The following new boolean filters have been introduced for dcim.Device and dcim.DeviceType:
+    * `console_ports`
+    * `power_ports`
+    * `pass_through_ports`
+* The field `interface_ordering` has been removed from the DeviceType serializer
+* Added a `description` field to the CircuitTermination serializer
+* Added `ipaddress_count` to InterfaceSerializer to show the count of assigned IP addresses for each interface
+* The `available-prefixes` and `available-ips` IPAM endpoints now return an HTTP 204 response instead of HTTP 400 when no new objects can be created
+* Filtering on null values now uses the string `null` instead of zero
+
+---
+
+v2.4.9 (2018-12-07)
+
+## Enhancements
+
+* [#2089](https://github.com/digitalocean/netbox/issues/2089) - Add SONET interface form factors
+* [#2495](https://github.com/digitalocean/netbox/issues/2495) - Enable deep-merging of config context data
+* [#2597](https://github.com/digitalocean/netbox/issues/2597) - Add FibreChannel SFP28 (32GFC) interface form factor
+
+## Bug Fixes
+
+* [#2400](https://github.com/digitalocean/netbox/issues/2400) - Correct representation of nested object assignment in API docs
+* [#2576](https://github.com/digitalocean/netbox/issues/2576) - Correct type for count_* fields in site API representation
+* [#2606](https://github.com/digitalocean/netbox/issues/2606) - Fixed filtering for interfaces with a virtual form factor
+* [#2611](https://github.com/digitalocean/netbox/issues/2611) - Fix error handling when assigning a clustered device to a different site
+* [#2613](https://github.com/digitalocean/netbox/issues/2613) - Decrease live search minimum characters to three
+* [#2615](https://github.com/digitalocean/netbox/issues/2615) - Tweak live search widget to use brief format for API requests
+* [#2623](https://github.com/digitalocean/netbox/issues/2623) - Removed the need to pass the model class to the rqworker process for webhooks
+* [#2634](https://github.com/digitalocean/netbox/issues/2634) - Enforce consistent representation of unnamed devices in rack view
+
+---
+
+v2.4.8 (2018-11-20)
+
+## Enhancements
+
+* [#2490](https://github.com/digitalocean/netbox/issues/2490) - Added bulk editing for config contexts
+* [#2557](https://github.com/digitalocean/netbox/issues/2557) - Added object view for tags
+
+## Bug Fixes
+
+* [#2473](https://github.com/digitalocean/netbox/issues/2473) - Fix encoding of long (>127 character) secrets
+* [#2558](https://github.com/digitalocean/netbox/issues/2558) - Filter on all tags when multiple are passed
+* [#2565](https://github.com/digitalocean/netbox/issues/2565) - Improved rendering of Markdown tables
+* [#2575](https://github.com/digitalocean/netbox/issues/2575) - Correct model specified for rack roles table
+* [#2588](https://github.com/digitalocean/netbox/issues/2588) - Catch all exceptions from failed NAPALM API Calls
+* [#2589](https://github.com/digitalocean/netbox/issues/2589) - Virtual machine API serializer should require cluster assignment
+
+---
+
+v2.4.7 (2018-11-06)
+
+## Enhancements
+
+* [#2388](https://github.com/digitalocean/netbox/issues/2388) - Enable filtering of devices/VMs by region
+* [#2427](https://github.com/digitalocean/netbox/issues/2427) - Allow filtering of interfaces by assigned VLAN or VLAN ID
+* [#2512](https://github.com/digitalocean/netbox/issues/2512) - Add device field to inventory item filter form
+
+## Bug Fixes
+
+* [#2502](https://github.com/digitalocean/netbox/issues/2502) - Allow duplicate VIPs inside a uniqueness-enforced VRF
+* [#2514](https://github.com/digitalocean/netbox/issues/2514) - Prevent new connections to already connected interfaces
+* [#2515](https://github.com/digitalocean/netbox/issues/2515) - Only use django-rq admin tmeplate if webhooks are enabled
+* [#2528](https://github.com/digitalocean/netbox/issues/2528) - Enable creating circuit terminations with interface assignment via API
+* [#2549](https://github.com/digitalocean/netbox/issues/2549) - Changed naming of `peer_device` and `peer_interface` on API /dcim/connected-device/ endpoint to use underscores
+
+---
+
+v2.4.6 (2018-10-05)
+
+## Enhancements
+
+* [#2479](https://github.com/digitalocean/netbox/issues/2479) - Add user permissions for creating/modifying API tokens
+* [#2487](https://github.com/digitalocean/netbox/issues/2487) - Return abbreviated API output when passed `?brief=1`
+
+## Bug Fixes
+
+* [#2393](https://github.com/digitalocean/netbox/issues/2393) - Fix Unicode support for CSV import under Python 2
+* [#2483](https://github.com/digitalocean/netbox/issues/2483) - Set max item count of API-populated form fields to MAX_PAGE_SIZE
+* [#2484](https://github.com/digitalocean/netbox/issues/2484) - Local config context not available on the Virtual Machine Edit Form
+* [#2485](https://github.com/digitalocean/netbox/issues/2485) - Fix cancel button when assigning a service to a device/VM
+* [#2491](https://github.com/digitalocean/netbox/issues/2491) - Fix exception when importing devices with invalid device type
+* [#2492](https://github.com/digitalocean/netbox/issues/2492) - Sanitize hostname and port values returned through LLDP
+
+---
+
+v2.4.5 (2018-10-02)
+
+## Enhancements
+
+* [#2392](https://github.com/digitalocean/netbox/issues/2392) - Implemented local context data for devices and virtual machines
+* [#2402](https://github.com/digitalocean/netbox/issues/2402) - Order and format JSON data in form fields
+* [#2432](https://github.com/digitalocean/netbox/issues/2432) - Link remote interface connections to the Interface view
+* [#2438](https://github.com/digitalocean/netbox/issues/2438) - API optimizations for tagged objects
+
+## Bug Fixes
+
+* [#2406](https://github.com/digitalocean/netbox/issues/2406) - Remove hard-coded limit of 1000 objects from API-populated form fields
+* [#2414](https://github.com/digitalocean/netbox/issues/2414) - Tags field missing from device/VM component creation forms
+* [#2442](https://github.com/digitalocean/netbox/issues/2442) - Nullify "next" link in API when limit=0 is passed
+* [#2443](https://github.com/digitalocean/netbox/issues/2443) - Enforce JSON object format when creating config contexts
+* [#2444](https://github.com/digitalocean/netbox/issues/2444) - Improve validation of interface MAC addresses
+* [#2455](https://github.com/digitalocean/netbox/issues/2455) - Ignore unique address enforcement for IPs with a shared/virtual role
+* [#2470](https://github.com/digitalocean/netbox/issues/2470) - Log the creation of device/VM components as object changes
+
+---
+
+v2.4.4 (2018-08-22)
+
+## Enhancements
+
+* [#2168](https://github.com/digitalocean/netbox/issues/2168) - Added Extreme SummitStack interface form factors
+* [#2356](https://github.com/digitalocean/netbox/issues/2356) - Include cluster site as read-only field in VirtualMachine serializer
+* [#2362](https://github.com/digitalocean/netbox/issues/2362) - Implemented custom admin site to properly handle BASE_PATH
+* [#2254](https://github.com/digitalocean/netbox/issues/2254) - Implemented searchability for Rack Groups
+
+## Bug Fixes
+
+* [#2353](https://github.com/digitalocean/netbox/issues/2353) - Handle `DoesNotExist` exception when deleting a device with connected interfaces
+* [#2354](https://github.com/digitalocean/netbox/issues/2354) - Increased maximum MTU for interfaces to 65536 bytes
+* [#2355](https://github.com/digitalocean/netbox/issues/2355) - Added item count to inventory tab on device view
+* [#2368](https://github.com/digitalocean/netbox/issues/2368) - Record change in device changelog when altering cluster assignment
+* [#2369](https://github.com/digitalocean/netbox/issues/2369) - Corrected time zone validation on site API serializer
+* [#2370](https://github.com/digitalocean/netbox/issues/2370) - Redirect to parent device after deleting device bays
+* [#2374](https://github.com/digitalocean/netbox/issues/2374) - Fix toggling display of IP addresses in virtual machine interfaces list
+* [#2378](https://github.com/digitalocean/netbox/issues/2378) - Corrected "edit" link for virtual machine interfaces
+
+---
+
+v2.4.3 (2018-08-09)
+
+## Enhancements
+
+* [#2333](https://github.com/digitalocean/netbox/issues/2333) - Added search filters for ConfigContexts
+
+## Bug Fixes
+
+* [#2334](https://github.com/digitalocean/netbox/issues/2334) - TypeError raised when WritableNestedSerializer receives a non-integer value
+* [#2335](https://github.com/digitalocean/netbox/issues/2335) - API requires group field when creating/updating a rack
+* [#2336](https://github.com/digitalocean/netbox/issues/2336) - Bulk deleting power outlets and console server ports from a device redirects to home page
+* [#2337](https://github.com/digitalocean/netbox/issues/2337) - Attempting to create the next available prefix within a parent assigned to a VRF raises an AssertionError
+* [#2340](https://github.com/digitalocean/netbox/issues/2340) - API requires manufacturer field when creating/updating an inventory item
+* [#2342](https://github.com/digitalocean/netbox/issues/2342) - IntegrityError raised when attempting to assign an invalid IP address as the primary for a VM
+* [#2344](https://github.com/digitalocean/netbox/issues/2344) - AttributeError when assigning VLANs to an interface on a device/VM not assigned to a site
+
+---
+
+v2.4.2 (2018-08-08)
+
+## Bug Fixes
+
+* [#2318](https://github.com/digitalocean/netbox/issues/2318) - ImportError when viewing a report
+* [#2319](https://github.com/digitalocean/netbox/issues/2319) - Extend ChoiceField to properly handle true/false choice keys
+* [#2320](https://github.com/digitalocean/netbox/issues/2320) - TypeError when dispatching a webhook with a secret key configured
+* [#2321](https://github.com/digitalocean/netbox/issues/2321) - Allow explicitly setting a null value on nullable ChoiceFields
+* [#2322](https://github.com/digitalocean/netbox/issues/2322) - Webhooks firing on non-enabled event types
+* [#2323](https://github.com/digitalocean/netbox/issues/2323) - DoesNotExist raised when deleting devices or virtual machines
+* [#2330](https://github.com/digitalocean/netbox/issues/2330) - Incorrect tab link in VRF changelog view
+
+---
+
+v2.4.1 (2018-08-07)
+
+## Bug Fixes
+
+* [#2303](https://github.com/digitalocean/netbox/issues/2303) - Always redirect to parent object when bulk editing/deleting components
+* [#2308](https://github.com/digitalocean/netbox/issues/2308) - Custom fields panel absent from object view in UI
+* [#2310](https://github.com/digitalocean/netbox/issues/2310) - False validation error on certain nested serializers
+* [#2311](https://github.com/digitalocean/netbox/issues/2311) - Redirect to parent after editing interface from device/VM view
+* [#2312](https://github.com/digitalocean/netbox/issues/2312) - Running a report yields a ValueError exception
+* [#2314](https://github.com/digitalocean/netbox/issues/2314) - Serialized representation of object in change log does not include assigned tags
+
+---
+
+v2.4.0 (2018-08-06)
+
+## New Features
+
+### Webhooks ([#81](https://github.com/digitalocean/netbox/issues/81))
+
+Webhooks enable NetBox to send a representation of an object every time one is created, updated, or deleted. Webhooks are sent from NetBox to external services via HTTP, and can be limited by object type. Services which receive a webhook can act on the data provided by NetBox to automate other tasks.
+
+Special thanks to [John Anderson](https://github.com/lampwins) for doing the heavy lifting for this feature!
+
+### Tagging ([#132](https://github.com/digitalocean/netbox/issues/132))
+
+Tags are free-form labels which can be assigned to a variety of objects in NetBox. Tags can be used to categorize and filter objects in addition to built-in and custom fields. Objects to which tags apply now include a `tags` field in the API.
+
+### Contextual Configuration Data ([#1349](https://github.com/digitalocean/netbox/issues/1349))
+
+Sometimes it is desirable to associate arbitrary data with a group of devices to aid in their configuration. (For example, you might want to associate a set of syslog servers for all devices at a particular site.) Context data enables the association of arbitrary data (expressed in JSON format) to devices and virtual machines grouped by region, site, role, platform, and/or tenancy. Context data is arranged hierarchically, so that data with a higher weight can be entered to override more general lower-weight data. Multiple instances of data are automatically merged by NetBox to present a single dictionary for each object.
+
+### Change Logging ([#1898](https://github.com/digitalocean/netbox/issues/1898))
+
+When an object is created, updated, or deleted, NetBox now automatically records a serialized representation of that object (similar to how it appears in the REST API) as well the event time and user account associated with the change.
+
+## Enhancements
+
+* [#238](https://github.com/digitalocean/netbox/issues/238) - Allow racks with the same name within a site (but in different groups)
+* [#971](https://github.com/digitalocean/netbox/issues/971) - Add a view to show all VLAN IDs available within a group
+* [#1673](https://github.com/digitalocean/netbox/issues/1673) - Added object/list views for services
+* [#1687](https://github.com/digitalocean/netbox/issues/1687) - Enabled custom fields for services
+* [#1739](https://github.com/digitalocean/netbox/issues/1739) - Enabled custom fields for secrets
+* [#1794](https://github.com/digitalocean/netbox/issues/1794) - Improved POST/PATCH representation of nested objects
+* [#2029](https://github.com/digitalocean/netbox/issues/2029) - Added optional NAPALM arguments to Platform model
+* [#2034](https://github.com/digitalocean/netbox/issues/2034) - Include the ID when showing nested interface connections (API change)
+* [#2118](https://github.com/digitalocean/netbox/issues/2118) - Added `latitude` and `longitude` fields to Site for GPS coordinates
+* [#2131](https://github.com/digitalocean/netbox/issues/2131) - Added `created` and `last_updated` fields to DeviceType
+* [#2157](https://github.com/digitalocean/netbox/issues/2157) - Fixed natural ordering of objects when sorted by name
+* [#2225](https://github.com/digitalocean/netbox/issues/2225) - Add "view elevations" button for site rack groups
+
+## Bug Fixes
+
+* [#2272](https://github.com/digitalocean/netbox/issues/2272) - Allow subdevice_role to be null on DeviceTypeSerializer"
+* [#2286](https://github.com/digitalocean/netbox/issues/2286) - Fixed "mark connected" button for PDU outlet connections
+
+## API Changes
+
+* Introduced the `/extras/config-contexts/`, `/extras/object-changes/`, and `/extras/tags/` API endpoints
+* API writes now return a nested representation of related objects (rather than only a numeric ID)
+* The dcim.DeviceType serializer now includes `created` and `last_updated` fields
+* The dcim.Site serializer now includes `latitude` and `longitude` fields
+* The ipam.Service and secrets.Secret serializers now include custom fields
+* The dcim.Platform serializer now includes a free-form (JSON) `napalm_args` field
+
+## Changes Since v2.4-beta1
+
+### Enhancements
+
+* [#2229](https://github.com/digitalocean/netbox/issues/2229) - Allow mapping of ConfigContexts to tenant groups
+* [#2259](https://github.com/digitalocean/netbox/issues/2259) - Add changelog tab to interface view
+* [#2264](https://github.com/digitalocean/netbox/issues/2264) - Added "map it" link for site GPS coordinates
+
+### Bug Fixes
+
+* [#2137](https://github.com/digitalocean/netbox/issues/2137) - Fixed JSON serialization of dates
+* [#2258](https://github.com/digitalocean/netbox/issues/2258) - Include changed object type on home page changelog
+* [#2265](https://github.com/digitalocean/netbox/issues/2265) - Include parent regions when filtering applicable ConfigContexts
+* [#2288](https://github.com/digitalocean/netbox/issues/2288) - Fix exception when assigning objects to a ConfigContext via the API
+* [#2296](https://github.com/digitalocean/netbox/issues/2296) - Fix AttributeError when creating a new object with tags assigned
+* [#2300](https://github.com/digitalocean/netbox/issues/2300) - Fix assignment of an interface to an IP address via API PATCH
+* [#2301](https://github.com/digitalocean/netbox/issues/2301) - Fix model validation on assignment of ManyToMany fields via API PATCH
+* [#2305](https://github.com/digitalocean/netbox/issues/2305) - Make VLAN fields optional when creating a VM interface via the API
+
+---
+
+v2.3.7 (2018-07-26)
+
+## Enhancements
+
+* [#2166](https://github.com/digitalocean/netbox/issues/2166) - Enable partial matching on device asset_tag during search
+
+## Bug Fixes
+
+* [#1977](https://github.com/digitalocean/netbox/issues/1977) - Fixed exception when creating a virtual chassis with a non-master device in position 1
+* [#1992](https://github.com/digitalocean/netbox/issues/1992) - Isolate errors when one of multiple NAPALM methods fails
+* [#2202](https://github.com/digitalocean/netbox/issues/2202) - Ditched half-baked concept of tenancy inheritance via VRF
+* [#2222](https://github.com/digitalocean/netbox/issues/2222) - IP addresses created via the `available-ips` API endpoint should have the same mask as their parent prefix (not /32)
+* [#2231](https://github.com/digitalocean/netbox/issues/2231) - Remove `get_absolute_url()` from DeviceRole (can apply to devices or VMs)
+* [#2250](https://github.com/digitalocean/netbox/issues/2250) - Include stat counters on report result navigation
+* [#2255](https://github.com/digitalocean/netbox/issues/2255) - Corrected display of results in reports list
+* [#2256](https://github.com/digitalocean/netbox/issues/2256) - Prevent navigation menu overlap when jumping to test results on report page
+* [#2257](https://github.com/digitalocean/netbox/issues/2257) - Corrected casting of RIR utilization stats as floats
+* [#2266](https://github.com/digitalocean/netbox/issues/2266) - Permit additional logging of exceptions beyond custom middleware
+
+---
+
+v2.3.6 (2018-07-16)
+
+## Enhancements
+
+* [#2107](https://github.com/digitalocean/netbox/issues/2107) - Added virtual chassis to global search
+* [#2125](https://github.com/digitalocean/netbox/issues/2125) - Show child status in device bay list
+
+## Bug Fixes
+
+* [#2214](https://github.com/digitalocean/netbox/issues/2214) - Error when assigning a VLAN to an interface on a VM in a cluster with no assigned site
+* [#2239](https://github.com/digitalocean/netbox/issues/2239) - Pin django-filter to version 1.1.0
+
+---
+
+v2.3.5 (2018-07-02)
+
+## Enhancements
+
+* [#2159](https://github.com/digitalocean/netbox/issues/2159) - Allow custom choice field to specify a default choice
+* [#2177](https://github.com/digitalocean/netbox/issues/2177) - Include device serial number in rack elevation pop-up
+* [#2194](https://github.com/digitalocean/netbox/issues/2194) - Added `address` filter to IPAddress model
+
+## Bug Fixes
+
+* [#1826](https://github.com/digitalocean/netbox/issues/1826) - Corrected description of security parameters under API definition
+* [#2021](https://github.com/digitalocean/netbox/issues/2021) - Fix recursion error when viewing API docs under Python 3.4
+* [#2064](https://github.com/digitalocean/netbox/issues/2064) - Disable calls to online swagger validator
+* [#2173](https://github.com/digitalocean/netbox/issues/2173) - Fixed IndexError when automatically allocating IP addresses from large IPv6 prefixes
+* [#2181](https://github.com/digitalocean/netbox/issues/2181) - Raise validation error on invalid `prefix_length` when allocating next-available prefix
+* [#2182](https://github.com/digitalocean/netbox/issues/2182) - ValueError can be raised when viewing the interface connections table
+* [#2191](https://github.com/digitalocean/netbox/issues/2191) - Added missing static choices to circuits and DCIM API endpoints
+* [#2192](https://github.com/digitalocean/netbox/issues/2192) - Prevent a 0U device from being assigned to a rack position
+
+---
+
+v2.3.4 (2018-06-07)
+
+## Bug Fixes
+
+* [#2066](https://github.com/digitalocean/netbox/issues/2066) - Catch `AddrFormatError` exception on invalid IP addresses
+* [#2075](https://github.com/digitalocean/netbox/issues/2075) - Enable tenant assignment when creating a rack reservation via the API
+* [#2083](https://github.com/digitalocean/netbox/issues/2083) - Add missing export button to rack roles list view
+* [#2087](https://github.com/digitalocean/netbox/issues/2087) - Don't overwrite existing vc_position of master device when creating a virtual chassis
+* [#2093](https://github.com/digitalocean/netbox/issues/2093) - Fix link to circuit termination in device interfaces table
+* [#2097](https://github.com/digitalocean/netbox/issues/2097) - Fixed queryset-based bulk deletion of clusters and regions
+* [#2098](https://github.com/digitalocean/netbox/issues/2098) - Fixed missing checkboxes for host devices in cluster view
+* [#2127](https://github.com/digitalocean/netbox/issues/2127) - Prevent non-conntectable interfaces from being connected
+* [#2143](https://github.com/digitalocean/netbox/issues/2143) - Accept null value for empty time zone field
+* [#2148](https://github.com/digitalocean/netbox/issues/2148) - Do not force timezone selection when editing sites in bulk
+* [#2150](https://github.com/digitalocean/netbox/issues/2150) - Fix display of LLDP neighbors when interface name contains a colon
+
+---
+
+v2.3.3 (2018-04-19)
+
+## Enhancements
+
+* [#1990](https://github.com/digitalocean/netbox/issues/1990) - Improved search function when assigning an IP address to an interface
+
+## Bug Fixes
+
+* [#1975](https://github.com/digitalocean/netbox/issues/1975) - Correct filtering logic for custom boolean fields
+* [#1988](https://github.com/digitalocean/netbox/issues/1988) - Order interfaces naturally when bulk renaming
+* [#1993](https://github.com/digitalocean/netbox/issues/1993) - Corrected status choices in site CSV import form
+* [#1999](https://github.com/digitalocean/netbox/issues/1999) - Added missing description field to site edit form
+* [#2012](https://github.com/digitalocean/netbox/issues/2012) - Fixed deselection of an IP address as the primary IP for its parent device/VM
+* [#2014](https://github.com/digitalocean/netbox/issues/2014) - Allow assignment of VLANs to VM interfaces via the API
+* [#2019](https://github.com/digitalocean/netbox/issues/2019) - Avoid casting oversized numbers as integers
+* [#2022](https://github.com/digitalocean/netbox/issues/2022) - Show 0 for zero-value fields on CSV export
+* [#2023](https://github.com/digitalocean/netbox/issues/2023) - Manufacturer should not be a required field when importing platforms
+* [#2037](https://github.com/digitalocean/netbox/issues/2037) - Fixed IndexError exception when attempting to create a new rack reservation
+
+---
+
+v2.3.2 (2018-03-22)
+
+## Enhancements
+
+* [#1586](https://github.com/digitalocean/netbox/issues/1586) - Extend bulk interface creation to support alphanumeric characters
+* [#1866](https://github.com/digitalocean/netbox/issues/1866) - Introduced AnnotatedMultipleChoiceField for filter forms
+* [#1930](https://github.com/digitalocean/netbox/issues/1930) - Switched to drf-yasg for Swagger API documentation
+* [#1944](https://github.com/digitalocean/netbox/issues/1944) - Enable assigning VLANs to virtual machine interfaces
+* [#1945](https://github.com/digitalocean/netbox/issues/1945) - Implemented a VLAN members view
+* [#1949](https://github.com/digitalocean/netbox/issues/1949) - Added a button to view elevations on rack groups list
+* [#1952](https://github.com/digitalocean/netbox/issues/1952) - Implemented a more robust mechanism for assigning VLANs to interfaces
+
+## Bug Fixes
+
+* [#1948](https://github.com/digitalocean/netbox/issues/1948) - Fix TypeError when attempting to add a member to an existing virtual chassis
+* [#1951](https://github.com/digitalocean/netbox/issues/1951) - Fix TypeError exception when importing platforms
+* [#1953](https://github.com/digitalocean/netbox/issues/1953) - Ignore duplicate IPs when calculating prefix utilization
+* [#1955](https://github.com/digitalocean/netbox/issues/1955) - Require a plaintext value when creating a new secret
+* [#1978](https://github.com/digitalocean/netbox/issues/1978) - Include all virtual chassis member interfaces in LLDP neighbors view
+* [#1980](https://github.com/digitalocean/netbox/issues/1980) - Fixed bug when trying to nullify a selection custom field under Python 2
+
+---
+
+v2.3.1 (2018-03-01)
+
+## Enhancements
+
+* [#1910](https://github.com/digitalocean/netbox/issues/1910) - Added filters for cluster group and cluster type
+
+## Bug Fixes
+
+* [#1915](https://github.com/digitalocean/netbox/issues/1915) - Redirect to device view after deleting a component
+* [#1919](https://github.com/digitalocean/netbox/issues/1919) - Prevent exception when attempting to create a virtual machine without selecting devices
+* [#1921](https://github.com/digitalocean/netbox/issues/1921) - Ignore ManyToManyFields when validating a new object created via the API
+* [#1924](https://github.com/digitalocean/netbox/issues/1924) - Include VID in VLAN lists when editing an interface
+* [#1926](https://github.com/digitalocean/netbox/issues/1926) - Prevent reassignment of parent device when bulk editing VC member interfaces
+* [#1927](https://github.com/digitalocean/netbox/issues/1927) - Include all VC member interfaces on A side when creating a new interface connection
+* [#1928](https://github.com/digitalocean/netbox/issues/1928) - Fixed form validation when modifying VLANs assigned to an interface
+* [#1934](https://github.com/digitalocean/netbox/issues/1934) - Fixed exception when rendering export template on an object type with custom fields assigned
+* [#1935](https://github.com/digitalocean/netbox/issues/1935) - Correct API validation of VLANs assigned to interfaces
+* [#1936](https://github.com/digitalocean/netbox/issues/1936) - Trigger validation error when attempting to create a virtual chassis without specifying member positions
+
+---
+
+v2.3.0 (2018-02-26)
+
+## New Features
+
+### Virtual Chassis ([#99](https://github.com/digitalocean/netbox/issues/99))
+
+A virtual chassis represents a set of physical devices with a shared control plane; for example, a stack of switches managed as a single device. Viewing the master device of a virtual chassis will show all member interfaces and IP addresses.
+
+### Interface VLAN Assignments ([#150](https://github.com/digitalocean/netbox/issues/150))
+
+Interfaces can now be assigned an 802.1Q mode (access or trunked) and associated with particular VLANs. Thanks to [John Anderson](https://github.com/lampwins) for his work on this!
+
+### Bulk Object Creation via the API ([#1553](https://github.com/digitalocean/netbox/issues/1553))
+
+The REST API now supports the creation of multiple objects of the same type using a single POST request. For example, to create multiple devices:
+
+```
+curl -X POST -H "Authorization: Token <TOKEN>" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/devices/ --data '[
+{"name": "device1", "device_type": 24, "device_role": 17, "site": 6},
+{"name": "device2", "device_type": 24, "device_role": 17, "site": 6},
+{"name": "device3", "device_type": 24, "device_role": 17, "site": 6},
+]'
+```
+
+Bulk creation is all-or-none: If any of the creations fails, the entire operation is rolled back.
+
+### Automatic Provisioning of Next Available Prefixes ([#1694](https://github.com/digitalocean/netbox/issues/1694))
+
+Similar to IP addresses, NetBox now supports automated provisioning of available prefixes from within a parent prefix. For example, to retrieve the next three available /28s within a parent /24:
+
+```
+curl -X POST -H "Authorization: Token <TOKEN>" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/ipam/prefixes/10153/available-prefixes/ --data '[
+{"prefix_length": 28},
+{"prefix_length": 28},
+{"prefix_length": 28}
+]'
+```
+
+If the parent prefix cannot accommodate all requested prefixes, the operation is cancelled and no new prefixes are created.
+
+### Bulk Renaming of Device/VM Components ([#1781](https://github.com/digitalocean/netbox/issues/1781))
+
+Device components (interfaces, console ports, etc.) can now be renamed in bulk via the web interface. This was implemented primarily to support the bulk renumbering of interfaces whose parent is part of a virtual chassis.
+
+## Enhancements
+
+* [#1283](https://github.com/digitalocean/netbox/issues/1283) - Added a `time_zone` field to the site model
+* [#1321](https://github.com/digitalocean/netbox/issues/1321) - Added `created` and `last_updated` fields for relevant models to their API serializers
+* [#1553](https://github.com/digitalocean/netbox/issues/1553) - Introduced support for bulk object creation via the API
+* [#1592](https://github.com/digitalocean/netbox/issues/1592) - Added tenancy assignment for rack reservations
+* [#1744](https://github.com/digitalocean/netbox/issues/1744) - Allow associating a platform with a specific manufacturer
+* [#1758](https://github.com/digitalocean/netbox/issues/1758) - Added a `status` field to the site model
+* [#1821](https://github.com/digitalocean/netbox/issues/1821) - Added a `description` field to the site model
+* [#1864](https://github.com/digitalocean/netbox/issues/1864) - Added a `status` field to the circuit model
+
+## Bug Fixes
+
+* [#1136](https://github.com/digitalocean/netbox/issues/1136) - Enforce model validation during bulk update
+* [#1645](https://github.com/digitalocean/netbox/issues/1645) - Simplified interface serialzier for IP addresses and optimized API view queryset
+* [#1838](https://github.com/digitalocean/netbox/issues/1838) - Fix KeyError when attempting to create a VirtualChassis with no devices selected
+* [#1847](https://github.com/digitalocean/netbox/issues/1847) - RecursionError when a virtual chasis master device has no name
+* [#1848](https://github.com/digitalocean/netbox/issues/1848) - Allow null value for interface encapsulation mode
+* [#1867](https://github.com/digitalocean/netbox/issues/1867) - Allow filtering on device status with multiple values
+* [#1881](https://github.com/digitalocean/netbox/issues/1881)* - Fixed bulk editing of interface 802.1Q settings
+* [#1884](https://github.com/digitalocean/netbox/issues/1884)* - Provide additional context to identify devices when creating/editing a virtual chassis
+* [#1907](https://github.com/digitalocean/netbox/issues/1907) - Allow removing an IP as the primary for a device when editing the IP directly
+
+\* New since v2.3-beta2
+
+## Breaking Changes
+
+* Constants representing device status have been renamed for clarity (for example, `STATUS_ACTIVE` is now `DEVICE_STATUS_ACTIVE`). Custom validation reports will need to be updated if they reference any of these constants.
+
+## API Changes
+
+* API creation calls now accept either a single JSON object or a list of JSON objects. If multiple objects are passed and one or more them fail validation, no objects will be created.
+* Added `created` and `last_updated` fields for objects inheriting from CreatedUpdatedModel.
+* Removed the `parent` filter for prefixes (use `within` or `within_include` instead).
+* The IP address serializer now includes only a minimal nested representation of the assigned interface (if any) and its parent device or virtual machine.
+* The rack reservation serializer now includes a nested representation of its owning user (as well as the assigned tenant, if any).
+* Added endpoints for virtual chassis and VC memberships.
+* Added `status`, `time_zone` (pytz format), and `description` fields to dcim.Site.
+* Added a `manufacturer` foreign key field on dcim.Platform.
+* Added a `status` field on circuits.Circuit.
+
+---
+
+v2.2.10 (2018-02-21)
+
+## Enhancements
+
+* [#78](https://github.com/digitalocean/netbox/issues/78) - Extended topology maps to support console and power connections
+* [#1693](https://github.com/digitalocean/netbox/issues/1693) - Allow specifying loose or exact matching for custom field filters
+* [#1714](https://github.com/digitalocean/netbox/issues/1714) - Standardized CSV export functionality for all object lists
+* [#1876](https://github.com/digitalocean/netbox/issues/1876) - Added explanatory title text to disabled NAPALM buttons on device view
+* [#1885](https://github.com/digitalocean/netbox/issues/1885) - Added a device filter field for primary IP
+
+## Bug Fixes
+
+* [#1858](https://github.com/digitalocean/netbox/issues/1858) - Include device/VM count for cluster list in global search results
+* [#1859](https://github.com/digitalocean/netbox/issues/1859) - Implemented support for line breaks within CSV fields
+* [#1860](https://github.com/digitalocean/netbox/issues/1860) - Do not populate initial values for custom fields when editing objects in bulk
+* [#1869](https://github.com/digitalocean/netbox/issues/1869) - Corrected ordering of VRFs with duplicate names
+* [#1886](https://github.com/digitalocean/netbox/issues/1886) - Allow setting the primary IPv4/v6 address for a virtual machine via the web UI
+
+---
+
+v2.2.9 (2018-01-31)
+
+## Enhancements
+
+* [#144](https://github.com/digitalocean/netbox/issues/144) - Implemented bulk import/edit/delete views for InventoryItems
+* [#1073](https://github.com/digitalocean/netbox/issues/1073) - Include prefixes/IPs from all VRFs when viewing the children of a container prefix in the global table
+* [#1366](https://github.com/digitalocean/netbox/issues/1366) - Enable searching for regions by name/slug
+* [#1406](https://github.com/digitalocean/netbox/issues/1406) - Display tenant description as title text in object tables
+* [#1824](https://github.com/digitalocean/netbox/issues/1824) - Add virtual machine count to platforms list
+* [#1835](https://github.com/digitalocean/netbox/issues/1835) - Consistent positioning of previous/next rack buttons
+
+## Bug Fixes
+
+* [#1621](https://github.com/digitalocean/netbox/issues/1621) - Tweaked LLDP interface name evaluation logic
+* [#1765](https://github.com/digitalocean/netbox/issues/1765) - Improved rendering of null options for model choice fields in filter forms
+* [#1807](https://github.com/digitalocean/netbox/issues/1807) - Populate VRF from parent when creating a new prefix
+* [#1809](https://github.com/digitalocean/netbox/issues/1809) - Populate tenant assignment from parent when creating a new prefix
+* [#1818](https://github.com/digitalocean/netbox/issues/1818) - InventoryItem API serializer no longer requires specifying a null value for items with no parent
+* [#1845](https://github.com/digitalocean/netbox/issues/1845) - Correct display of VMs in list with no role assigned
+* [#1850](https://github.com/digitalocean/netbox/issues/1850) - Fix TypeError when attempting IP address import if only unnamed devices exist
+
+---
+
+v2.2.8 (2017-12-20)
+
+## Enhancements
+
+* [#1771](https://github.com/digitalocean/netbox/issues/1771) - Added name filter for racks
+* [#1772](https://github.com/digitalocean/netbox/issues/1772) - Added position filter for devices
+* [#1773](https://github.com/digitalocean/netbox/issues/1773) - Moved child prefixes table to its own view
+* [#1774](https://github.com/digitalocean/netbox/issues/1774) - Include a button to refine search results for all object types under global search
+* [#1784](https://github.com/digitalocean/netbox/issues/1784) - Added `cluster_type` filters for virtual machines
+
+## Bug Fixes
+
+* [#1766](https://github.com/digitalocean/netbox/issues/1766) - Fixed display of "select all" button on device power outlets list
+* [#1767](https://github.com/digitalocean/netbox/issues/1767) - Use proper template for 404 responses
+* [#1778](https://github.com/digitalocean/netbox/issues/1778) - Preserve initial VRF assignment when adding IP addresses in bulk from a prefix
+* [#1783](https://github.com/digitalocean/netbox/issues/1783) - Added `vm_role` filter for device roles
+* [#1785](https://github.com/digitalocean/netbox/issues/1785) - Omit filter forms from browsable API
+* [#1787](https://github.com/digitalocean/netbox/issues/1787) - Added missing site field to virtualization cluster CSV export
+
+---
+
+v2.2.7 (2017-12-07)
+
+## Enhancements
+
+* [#1722](https://github.com/digitalocean/netbox/issues/1722) - Added virtual machine count to site view
+* [#1737](https://github.com/digitalocean/netbox/issues/1737) - Added a `contains` API filter to find all prefixes containing a given IP or prefix
+
+## Bug Fixes
+
+* [#1712](https://github.com/digitalocean/netbox/issues/1712) - Corrected tenant inheritance for new IP addresses created from a parent prefix
+* [#1721](https://github.com/digitalocean/netbox/issues/1721) - Differentiated child IP count from utilization percentage for prefixes
+* [#1740](https://github.com/digitalocean/netbox/issues/1740) - Delete session_key cookie on logout
+* [#1741](https://github.com/digitalocean/netbox/issues/1741) - Fixed Unicode support for secret plaintexts
+* [#1743](https://github.com/digitalocean/netbox/issues/1743) - Include number of instances for device types in global search
+* [#1751](https://github.com/digitalocean/netbox/issues/1751) - Corrected filtering for IPv6 addresses containing letters
+* [#1756](https://github.com/digitalocean/netbox/issues/1756) - Improved natural ordering of console server ports and power outlets
+
+---
+
+v2.2.6 (2017-11-16)
+
+## Enhancements
+
+* [#1669](https://github.com/digitalocean/netbox/issues/1669) - Clicking "add an IP" from the prefix view will default to the first available IP within the prefix
+
+## Bug Fixes
+
+* [#1397](https://github.com/digitalocean/netbox/issues/1397) - Display global search in navigation menu unless display is less than 1200px wide
+* [#1599](https://github.com/digitalocean/netbox/issues/1599) - Reduce mobile cut-off for navigation menu to 960px
+* [#1715](https://github.com/digitalocean/netbox/issues/1715) - Added missing import buttons on object lists
+* [#1717](https://github.com/digitalocean/netbox/issues/1717) - Fixed interface validation for virtual machines
+* [#1718](https://github.com/digitalocean/netbox/issues/1718) - Set empty label to "Global" or VRF field in IP assignment form
+
+---
+
+v2.2.5 (2017-11-14)
+
+## Enhancements
+
+* [#1512](https://github.com/digitalocean/netbox/issues/1512) - Added a view to search for an IP address being assigned to an interface
+* [#1679](https://github.com/digitalocean/netbox/issues/1679) - Added IP address roles to device/VM interface lists
+* [#1683](https://github.com/digitalocean/netbox/issues/1683) - Replaced default 500 handler with custom middleware to provide preliminary troubleshooting assistance
+* [#1684](https://github.com/digitalocean/netbox/issues/1684) - Replaced prefix `parent` filter with `within` and `within_include`
+
+## Bug Fixes
+
+* [#1471](https://github.com/digitalocean/netbox/issues/1471) - Correct bulk selection of IP addresses within a prefix assigned to a VRF
+* [#1642](https://github.com/digitalocean/netbox/issues/1642) - Validate device type classification when creating console server ports and power outlets
+* [#1650](https://github.com/digitalocean/netbox/issues/1650) - Correct numeric ordering for interfaces with no alphabetic type
+* [#1676](https://github.com/digitalocean/netbox/issues/1676) - Correct filtering of child prefixes upon bulk edit/delete from the parent prefix view
+* [#1689](https://github.com/digitalocean/netbox/issues/1689) - Disregard IP address mask when filtering for child IPs of a prefix
+* [#1696](https://github.com/digitalocean/netbox/issues/1696) - Fix for NAPALM v2.0+
+* [#1699](https://github.com/digitalocean/netbox/issues/1699) - Correct nested representation in the API of primary IPs for virtual machines and add missing primary_ip property
+* [#1701](https://github.com/digitalocean/netbox/issues/1701) - Fixed validation in `extras/0008_reports.py` migration for certain versions of PostgreSQL
+* [#1703](https://github.com/digitalocean/netbox/issues/1703) - Added API serializer validation for custom integer fields
+* [#1705](https://github.com/digitalocean/netbox/issues/1705) - Fixed filtering of devices with a status of offline
+
+---
+
+v2.2.4 (2017-10-31)
+
+## Bug Fixes
+
+* [#1670](https://github.com/digitalocean/netbox/issues/1670) - Fixed server error when calling certain filters (regression from #1649)
+
+---
+
+v2.2.3 (2017-10-31)
+
+## Enhancements
+
+* [#999](https://github.com/digitalocean/netbox/issues/999) - Display devices on which circuits are terminated in circuits list
+* [#1491](https://github.com/digitalocean/netbox/issues/1491) - Added initial data for the virtualization app
+* [#1620](https://github.com/digitalocean/netbox/issues/1620) - Loosen IP address search filter to match all IPs that start with the given string
+* [#1631](https://github.com/digitalocean/netbox/issues/1631) - Added a `post_run` method to the Report class
+* [#1666](https://github.com/digitalocean/netbox/issues/1666) - Allow modifying the owner of a rack reservation
+
+## Bug Fixes
+
+* [#1513](https://github.com/digitalocean/netbox/issues/1513) - Correct filtering of custom field choices
+* [#1603](https://github.com/digitalocean/netbox/issues/1603) - Hide selection checkboxes for tables with no available actions
+* [#1618](https://github.com/digitalocean/netbox/issues/1618) - Allow bulk deletion of all virtual machines
+* [#1619](https://github.com/digitalocean/netbox/issues/1619) - Correct text-based filtering of IP network and address fields
+* [#1624](https://github.com/digitalocean/netbox/issues/1624) - Add VM count to device roles table
+* [#1634](https://github.com/digitalocean/netbox/issues/1634) - Cluster should not be a required field when importing child devices
+* [#1649](https://github.com/digitalocean/netbox/issues/1649) - Correct filtering on null values (e.g. ?tenant_id=0) for django-filters v1.1.0+
+* [#1653](https://github.com/digitalocean/netbox/issues/1653) - Remove outdated description for DeviceType's `is_network_device` flag
+* [#1664](https://github.com/digitalocean/netbox/issues/1664) - Added missing `serial` field in default rack CSV export
+
+---
+
+v2.2.2 (2017-10-17)
+
+## Enhancements
+
+* [#1580](https://github.com/digitalocean/netbox/issues/1580) - Allow cluster assignment when bulk importing devices
+* [#1587](https://github.com/digitalocean/netbox/issues/1587) - Add primary IP column for virtual machines in global search results
+
+## Bug Fixes
+
+* [#1498](https://github.com/digitalocean/netbox/issues/1498) - Avoid duplicating nodes when generating topology maps
+* [#1579](https://github.com/digitalocean/netbox/issues/1579) - Devices already assigned to a cluster cannot be added to a different cluster
+* [#1582](https://github.com/digitalocean/netbox/issues/1582) - Add `virtual_machine` attribute to IPAddress
+* [#1584](https://github.com/digitalocean/netbox/issues/1584) - Colorized virtual machine role column
+* [#1585](https://github.com/digitalocean/netbox/issues/1585) - Fixed slug-based filtering of virtual machines
+* [#1605](https://github.com/digitalocean/netbox/issues/1605) - Added clusters and virtual machines to object list for global search
+* [#1609](https://github.com/digitalocean/netbox/issues/1609) - Added missing `virtual_machine` field to IP address interface serializer
+
+---
+
+v2.2.1 (2017-10-12)
+
+## Bug Fixes
+
+* [#1576](https://github.com/digitalocean/netbox/issues/1576) - Moved PostgreSQL validation logic into the relevant migration (fixed ImproperlyConfigured exception on init)
+
+---
+
+v2.2.0 (2017-10-12)
+
+**Note:** This release requires PostgreSQL 9.4 or higher. Do not attempt to upgrade unless you are running at least PostgreSQL 9.4.
+
+**Note:** The release replaces the deprecated pycrypto library with [pycryptodome](https://github.com/Legrandin/pycryptodome). The upgrade script has been extended to automatically uninstall the old library, but please verify your installed packages with `pip freeze | grep pycrypto` if you run into problems.
+
+## New Features
+
+### Virtual Machines and Clusters ([#142](https://github.com/digitalocean/netbox/issues/142))
+
+Our second-most popular feature request has arrived! NetBox now supports the creation of virtual machines, which can be assigned virtual interfaces and IP addresses. VMs are arranged into clusters, each of which has a type and (optionally) a group.
+
+### Custom Validation Reports ([#1511](https://github.com/digitalocean/netbox/issues/1511))
+
+Users can now create custom reports which are run to validate data in NetBox. Reports work very similar to Python unit tests: Each report inherits from NetBox's Report class and contains one or more test method. Reports can be run and retrieved via the web UI, API, or CLI. See [the docs](http://netbox.readthedocs.io/en/stable/miscellaneous/reports/) for more info.
+
+## Enhancements
+
+* [#494](https://github.com/digitalocean/netbox/issues/494) - Include asset tag in device info pop-up on rack elevation
+* [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added a `serial` field to the rack model
+* [#1479](https://github.com/digitalocean/netbox/issues/1479) - Added an IP address role for CARP
+* [#1506](https://github.com/digitalocean/netbox/issues/1506) - Extended rack facility ID field from 30 to 50 characters
+* [#1510](https://github.com/digitalocean/netbox/issues/1510) - Added ability to search by name when adding devices to a cluster
+* [#1527](https://github.com/digitalocean/netbox/issues/1527) - Replace deprecated pycrypto library with pycryptodome
+* [#1551](https://github.com/digitalocean/netbox/issues/1551) - Added API endpoints listing static field choices for each app
+* [#1556](https://github.com/digitalocean/netbox/issues/1556) - Added CPAK, CFP2, and CFP4 100GE interface form factors
+* Added CSV import views for all object types
+
+## Bug Fixes
+
+* [#1550](https://github.com/digitalocean/netbox/issues/1550) - Corrected interface connections link in navigation menu
+* [#1554](https://github.com/digitalocean/netbox/issues/1554) - Don't require form_factor when creating an interface assigned to a virtual machine
+* [#1557](https://github.com/digitalocean/netbox/issues/1557) - Added filtering for virtual machine interfaces
+* [#1567](https://github.com/digitalocean/netbox/issues/1567) - Prompt user for session key when importing secrets
+
+## API Changes
+
+* Introduced the virtualization app and its associated endpoints at `/api/virtualization`
+* Added the `/api/extras/reports` endpoint for fetching and running reports
+* The `ipam.Service` and `dcim.Interface` models now have a `virtual_machine` field in addition to the `device` field. Only one of the two fields may be defined for each object
+* Added a `vm_role` field to `dcim.DeviceRole`, which indicates whether a role is suitable for assigned to a virtual machine
+* Added a `serial` field to 'dcim.Rack` for serial numbers
+* Each app now has a `_choices` endpoint, which lists the available options for all model field with static choices (e.g. interface form factors)
+
+---
+
+v2.1.6 (2017-10-11)
+
+## Enhancements
+
+* [#1548](https://github.com/digitalocean/netbox/issues/1548) - Automatically populate tenant assignment when adding an IP address from the prefix view
+* [#1561](https://github.com/digitalocean/netbox/issues/1561) - Added primary IP to the devices table in global search
+* [#1563](https://github.com/digitalocean/netbox/issues/1563) - Made necessary updates for Django REST Framework v3.7.0
+
+---
+
+v2.1.5 (2017-09-25)
+
+## Enhancements
+
+* [#1484](https://github.com/digitalocean/netbox/issues/1484) - Added individual "add VLAN" buttons on the VLAN groups list
+* [#1485](https://github.com/digitalocean/netbox/issues/1485) - Added `BANNER_LOGIN` configuration setting to display a banner on the login page
+* [#1499](https://github.com/digitalocean/netbox/issues/1499) - Added utilization graph to child prefixes table
+* [#1523](https://github.com/digitalocean/netbox/issues/1523) - Improved the natural ordering of interfaces (thanks to [@tarkatronic](https://github.com/tarkatronic))
+* [#1536](https://github.com/digitalocean/netbox/issues/1536) - Improved formatting of aggregate prefix statistics
+
+## Bug Fixes
+
+* [#1469](https://github.com/digitalocean/netbox/issues/1469) - Allow a NAT IP to be assigned as the primary IP for a device
+* [#1472](https://github.com/digitalocean/netbox/issues/1472) - Prevented truncation when displaying secret strings containing HTML characters
+* [#1486](https://github.com/digitalocean/netbox/issues/1486) - Ignore subinterface IDs when validating LLDP neighbor connections
+* [#1489](https://github.com/digitalocean/netbox/issues/1489) - Corrected server error on validation of empty required custom field
+* [#1507](https://github.com/digitalocean/netbox/issues/1507) - Fixed error when creating the next available IP from a prefix within a VRF
+* [#1520](https://github.com/digitalocean/netbox/issues/1520) - Redirect on GET request to bulk edit/delete views
+* [#1522](https://github.com/digitalocean/netbox/issues/1522) - Removed object create/edit forms from the browsable API
+
+---
+
+v2.1.4 (2017-08-30)
+
+## Enhancements
+
+* [#1326](https://github.com/digitalocean/netbox/issues/1326) - Added dropdown widget with common values for circuit speed fields
+* [#1341](https://github.com/digitalocean/netbox/issues/1341) - Added a `MEDIA_ROOT` configuration setting to specify where uploaded files are stored on disk
+* [#1376](https://github.com/digitalocean/netbox/issues/1376) - Ignore anycast addresses when detecting duplicate IPs
+* [#1402](https://github.com/digitalocean/netbox/issues/1402) - Increased max length of name field for device components
+* [#1431](https://github.com/digitalocean/netbox/issues/1431) - Added interface form factor for 10GBASE-CX4
+* [#1432](https://github.com/digitalocean/netbox/issues/1432) - Added a `commit_rate` field to the circuits list search form
+* [#1460](https://github.com/digitalocean/netbox/issues/1460) - Hostnames with no domain are now acceptable in custom URL fields
+
+## Bug Fixes
+
+* [#1429](https://github.com/digitalocean/netbox/issues/1429) - Fixed uptime formatting on device status page
+* [#1433](https://github.com/digitalocean/netbox/issues/1433) - Fixed `devicetype_id` filter for DeviceType components
+* [#1443](https://github.com/digitalocean/netbox/issues/1443) - Fixed API validation error involving custom field data
+* [#1458](https://github.com/digitalocean/netbox/issues/1458) - Corrected permission name on prefix/VLAN roles list
+
+---
+
+v2.1.3 (2017-08-15)
+
+## Bug Fixes
+
+* [#1330](https://github.com/digitalocean/netbox/issues/1330) - Raise validation error when assigning an unrelated IP as the primary IP for a device
+* [#1389](https://github.com/digitalocean/netbox/issues/1389) - Avoid splitting carat/prefix on prefix list
+* [#1400](https://github.com/digitalocean/netbox/issues/1400) - Removed redundant display of assigned device interface from IP address list
+* [#1414](https://github.com/digitalocean/netbox/issues/1414) - Selecting a site from the rack filters automatically updates the available rack groups
+* [#1419](https://github.com/digitalocean/netbox/issues/1419) - Allow editing image attachments without re-uploading an image
+* [#1420](https://github.com/digitalocean/netbox/issues/1420) - Exclude virtual interfaces from device LLDP neighbors view
+* [#1421](https://github.com/digitalocean/netbox/issues/1421) - Improved model validation logic for API serializers
+* Fixed page title capitalization in the browsable API
+
+---
+
+v2.1.2 (2017-08-04)
+
+## Enhancements
+
+* [#992](https://github.com/digitalocean/netbox/issues/992) - Allow the creation of multiple services per device with the same protocol and port
+* Tweaked navigation menu styling
+
+## Bug Fixes
+
+* [#1388](https://github.com/digitalocean/netbox/issues/1388) - Fixed server error when searching globally for IPs/prefixes (rolled back #1379)
+* [#1390](https://github.com/digitalocean/netbox/issues/1390) - Fixed IndexError when viewing available IPs within large IPv6 prefixes
+
+---
+
+v2.1.1 (2017-08-02)
+
+## Enhancements
+
+* [#893](https://github.com/digitalocean/netbox/issues/893) - Allow filtering by null values for NullCharacterFields (e.g. return only unnamed devices)
+* [#1368](https://github.com/digitalocean/netbox/issues/1368) - Render reservations in rack elevations view
+* [#1374](https://github.com/digitalocean/netbox/issues/1374) - Added NAPALM_ARGS and NAPALM_TIMEOUT configiuration parameters
+* [#1375](https://github.com/digitalocean/netbox/issues/1375) - Renamed `NETBOX_USERNAME` and `NETBOX_PASSWORD` configuration parameters to `NAPALM_USERNAME` and `NAPALM_PASSWORD`
+* [#1379](https://github.com/digitalocean/netbox/issues/1379) - Allow searching devices by interface MAC address in global search
+
+## Bug Fixes
+
+* [#461](https://github.com/digitalocean/netbox/issues/461) - Display a validation error when attempting to assigning a new child device to a rack face/position
+* [#1385](https://github.com/digitalocean/netbox/issues/1385) - Connected device API endpoint no longer requires authentication if `LOGIN_REQUIRED` is False
+
+---
+
+v2.1.0 (2017-07-25)
+
+## New Features
+
+### IP Address Roles ([#819](https://github.com/digitalocean/netbox/issues/819))
+
+The IP address model now supports the assignment of a functional role to help identify special-purpose IPs. These include:
+
+* Loopback
+* Secondary
+* Anycast
+* VIP
+* VRRP
+* HSRP
+* GLBP
+
+### Automatic Provisioning of Next Available IP ([#1246](https://github.com/digitalocean/netbox/issues/1246))
+
+A new API endpoint has been added at `/api/ipam/prefixes/<pk>/available-ips/`. A GET request to this endpoint will return a list of available IP addresses within the prefix (up to the pagination limit). A POST request will automatically create and return the next available IP address.
+
+### NAPALM Integration ([#1348](https://github.com/digitalocean/netbox/issues/1348))
+
+The [NAPALM automation](https://napalm-automation.net/) library provides an abstracted interface for pulling live data (e.g. uptime, software version, running config, LLDP neighbors, etc.) from network devices. The NetBox API has been extended to support executing read-only NAPALM methods on devices defined in NetBox. To enable this functionality, ensure that NAPALM has been installed (`pip install napalm`) and the `NETBOX_USERNAME` and `NETBOX_PASSWORD` [configuration parameters](http://netbox.readthedocs.io/en/stable/configuration/optional-settings/#netbox_username) have been set in configuration.py.
+
+## Enhancements
+
+* [#838](https://github.com/digitalocean/netbox/issues/838) - Display details of all objects being edited/deleted in bulk
+* [#1041](https://github.com/digitalocean/netbox/issues/1041) - Added enabled and MTU fields to the interface model
+* [#1121](https://github.com/digitalocean/netbox/issues/1121) - Added asset_tag and description fields to the InventoryItem model
+* [#1141](https://github.com/digitalocean/netbox/issues/1141) - Include RD when listing VRFs in a form selection field
+* [#1203](https://github.com/digitalocean/netbox/issues/1203) - Implemented query filters for all models
+* [#1218](https://github.com/digitalocean/netbox/issues/1218) - Added IEEE 802.11 wireless interface types
+* [#1269](https://github.com/digitalocean/netbox/issues/1269) - Added circuit termination to interface serializer
+* [#1320](https://github.com/digitalocean/netbox/issues/1320) - Removed checkbox from confirmation dialog
+
+## Bug Fixes
+
+* [#1079](https://github.com/digitalocean/netbox/issues/1079) - Order interfaces naturally via API
+* [#1285](https://github.com/digitalocean/netbox/issues/1285) - Enforce model validation when creating/editing objects via the API
+* [#1358](https://github.com/digitalocean/netbox/issues/1358) - Correct VRF example values in IP/prefix import forms
+* [#1362](https://github.com/digitalocean/netbox/issues/1362) - Raise validation error when attempting to create an API key that's too short
+* [#1371](https://github.com/digitalocean/netbox/issues/1371) - Extend DeviceSerializer.parent_device to include standard fields
+
+## API changes
+
+* Added a new API endpoint which makes [NAPALM](https://github.com/napalm-automation/napalm) accessible via NetBox
+* Device components (console ports, power ports, interfaces, etc.) can only be filtered by a single device name or ID. This limitation was necessary to allow the natural ordering of interfaces according to the device's parent device type.
+* Added two new fields to the interface serializer: `enabled` (boolean) and `mtu` (unsigned integer)
+* Modified the interface serializer to include three discrete fields relating to connections: `is_connected` (boolean), `interface_connection`, and `circuit_termination`
+* Added two new fields to the inventory item serializer: `asset_tag` and `description`
+* Added "wireless" to interface type filter (in addition to physical, virtual, and LAG)
+* Added a new endpoint at /api/ipam/prefixes/<pk>/available-ips/ to retrieve or create available IPs within a prefix
+* Extended `parent_device` on DeviceSerializer to include the `url` and `display_name` of the parent Device, and the `url` of the DeviceBay
+
+---
+
+v2.0.10 (2017-07-14)
+
+## Bug Fixes
+
+* [#1312](https://github.com/digitalocean/netbox/issues/1312) - Catch error when attempting to activate a user key with an invalid private key
+* [#1333](https://github.com/digitalocean/netbox/issues/1333) - Corrected label on is_console_server field of DeviceType bulk edit form
+* [#1338](https://github.com/digitalocean/netbox/issues/1338) - Allow importing prefixes with "container" status
+* [#1339](https://github.com/digitalocean/netbox/issues/1339) - Fixed disappearing checkbox column under django-tables2 v1.7+
+* [#1342](https://github.com/digitalocean/netbox/issues/1342) - Allow designation of users and groups when creating/editing a secret role
+
+---
+
+v2.0.9 (2017-07-10)
+
+## Bug Fixes
+
+* [#1319](https://github.com/digitalocean/netbox/issues/1319) - Fixed server error when attempting to create console/power connections
+* [#1325](https://github.com/digitalocean/netbox/issues/1325) - Retain interface attachment when editing a circuit termination
+
+---
+
+v2.0.8 (2017-07-05)
+
+## Enhancements
+
+* [#1298](https://github.com/digitalocean/netbox/issues/1298) - Calculate prefix utilization based on its status (container or non-container)
+* [#1303](https://github.com/digitalocean/netbox/issues/1303) - Highlight installed interface connections in green on device view
+* [#1315](https://github.com/digitalocean/netbox/issues/1315) - Enforce lowercase file extensions for image attachments
+
+## Bug Fixes
+
+* [#1279](https://github.com/digitalocean/netbox/issues/1279) - Fix primary_ip assignment during IP address import
+* [#1281](https://github.com/digitalocean/netbox/issues/1281) - Show LLDP neighbors tab on device view only if necessary conditions are met
+* [#1282](https://github.com/digitalocean/netbox/issues/1282) - Fixed tooltips on "mark connected/planned" toggle buttons for device connections
+* [#1288](https://github.com/digitalocean/netbox/issues/1288) - Corrected permission name for deleting image attachments
+* [#1289](https://github.com/digitalocean/netbox/issues/1289) - Retain inside NAT assignment when editing an IP address
+* [#1297](https://github.com/digitalocean/netbox/issues/1297) - Allow passing custom field choice selection PKs to API as string-quoted integers
+* [#1299](https://github.com/digitalocean/netbox/issues/1299) - Corrected permission name for adding services to devices
+
+---
+
+v2.0.7 (2017-06-15)
+
+## Enhancements
+
+* [#626](https://github.com/digitalocean/netbox/issues/626) - Added bulk disconnect function for console/power/interface connections on device view
+
+## Bug Fixes
+
+* [#1238](https://github.com/digitalocean/netbox/issues/1238) - Fix error when editing an IP with a NAT assignment which has no assigned device
+* [#1263](https://github.com/digitalocean/netbox/issues/1263) - Differentiate add and edit permissions for objects
+* [#1265](https://github.com/digitalocean/netbox/issues/1265) - Fix console/power/interface connection validation when selecting a device via live search
+* [#1266](https://github.com/digitalocean/netbox/issues/1266) - Prevent terminating a circuit to an already-connected interface
+* [#1268](https://github.com/digitalocean/netbox/issues/1268) - Fix CSV import error under Python 3
+* [#1273](https://github.com/digitalocean/netbox/issues/1273) - Corrected status choices in IP address import form
+* [#1274](https://github.com/digitalocean/netbox/issues/1274) - Exclude unterminated circuits from topology maps
+* [#1275](https://github.com/digitalocean/netbox/issues/1275) - Raise validation error on prefix import when multiple VLANs are found
+
+---
+
+v2.0.6 (2017-06-12)
+
+## Enhancements
+
+* [#40](https://github.com/digitalocean/netbox/issues/40) - Added IP utilization graph to prefix list
+* [#704](https://github.com/digitalocean/netbox/issues/704) - Allow filtering VLANs by group when editing prefixes
+* [#913](https://github.com/digitalocean/netbox/issues/913) - Added headers to object CSV exports
+* [#990](https://github.com/digitalocean/netbox/issues/990) - Enable logging configuration in configuration.py
+* [#1180](https://github.com/digitalocean/netbox/issues/1180) - Simplified the process of finding related devices when viewing a device
+
+## Bug Fixes
+
+* [#1253](https://github.com/digitalocean/netbox/issues/1253) - Improved `upgrade.sh` to allow forcing Python2
+
+---
+
+v2.0.5 (2017-06-08)
+
+## Notes
+
+The maximum number of objects an API consumer can request has been set to 1000 (e.g. `?limit=1000`). This limit can be modified by defining `MAX_PAGE_SIZE` in confgiuration.py. (To remove this limit, set `MAX_PAGE_SIZE=0`.)
+
+## Enhancements
+
+* [#655](https://github.com/digitalocean/netbox/issues/655) - Implemented header-based CSV import of objects
+* [#1190](https://github.com/digitalocean/netbox/issues/1190) - Allow partial string matching when searching on custom fields
+* [#1237](https://github.com/digitalocean/netbox/issues/1237) - Enabled setting limit=0 to disable pagination in API requests; added `MAX_PAGE_SIZE` configuration setting
+
+## Bug Fixes
+
+* [#837](https://github.com/digitalocean/netbox/issues/837) - Enforce uniqueness where applicable during bulk import of IP addresses
+* [#1226](https://github.com/digitalocean/netbox/issues/1226) - Improved validation for custom field values submitted via the API
+* [#1232](https://github.com/digitalocean/netbox/issues/1232) - Improved rack space validation on bulk import of devices (see #655)
+* [#1235](https://github.com/digitalocean/netbox/issues/1235) - Fix permission name for adding/editing inventory items
+* [#1236](https://github.com/digitalocean/netbox/issues/1236) - Truncate rack names in elevations list; add facility ID
+* [#1239](https://github.com/digitalocean/netbox/issues/1239) - Fix server error when creating VLANGroup via API
+* [#1243](https://github.com/digitalocean/netbox/issues/1243) - Catch ValueError in IP-based object filters
+* [#1244](https://github.com/digitalocean/netbox/issues/1244) - Corrected "device" secrets filter to accept a device name
+
+---
+
+v2.0.4 (2017-05-25)
+
+## Bug Fixes
+
+* [#1206](https://github.com/digitalocean/netbox/issues/1206) - Fix redirection in admin UI after activating secret keys when BASE_PATH is set
+* [#1207](https://github.com/digitalocean/netbox/issues/1207) - Include nested LAG serializer when showing interface connections (API)
+* [#1210](https://github.com/digitalocean/netbox/issues/1210) - Fix TemplateDoesNotExist errors on browsable API views
+* [#1212](https://github.com/digitalocean/netbox/issues/1212) - Allow assigning new VLANs to global VLAN groups
+* [#1213](https://github.com/digitalocean/netbox/issues/1213) - Corrected table header ordering links on object list views
+* [#1214](https://github.com/digitalocean/netbox/issues/1214) - Add status to list of required fields on child device import form
+* [#1219](https://github.com/digitalocean/netbox/issues/1219) - Fix image attachment URLs when BASE_PATH is set
+* [#1220](https://github.com/digitalocean/netbox/issues/1220) - Suppressed innocuous warning about untracked migrations under Python 3
+* [#1229](https://github.com/digitalocean/netbox/issues/1229) - Fix validation error on forms where API search is used
+
+---
+
+v2.0.3 (2017-05-18)
+
+## Enhancements
+
+* [#1196](https://github.com/digitalocean/netbox/issues/1196) - Added a lag_id filter to the API interfaces view
+* [#1198](https://github.com/digitalocean/netbox/issues/1198) - Allow filtering unracked devices on device list
+
+## Bug Fixes
+
+* [#1157](https://github.com/digitalocean/netbox/issues/1157) - Hide nav menu search bar on small displays
+* [#1186](https://github.com/digitalocean/netbox/issues/1186) - Corrected VLAN edit form so that site assignment is not required
+* [#1187](https://github.com/digitalocean/netbox/issues/1187) - Fixed table pagination by introducing a custom table template
+* [#1188](https://github.com/digitalocean/netbox/issues/1188) - Serialize interface LAG as nested objected (API)
+* [#1189](https://github.com/digitalocean/netbox/issues/1189) - Enforce consistent ordering of objects returned by a global search
+* [#1191](https://github.com/digitalocean/netbox/issues/1191) - Bulk selection of IPs under a prefix incorrect when "select all" is used
+* [#1195](https://github.com/digitalocean/netbox/issues/1195) - Unable to create an interface connection when searching for peer device
+* [#1197](https://github.com/digitalocean/netbox/issues/1197) - Fixed status assignment during bulk import of devices, prefixes, IPs, and VLANs
+* [#1199](https://github.com/digitalocean/netbox/issues/1199) - Bulk import of secrets does not prompt user to generate a session key
+* [#1200](https://github.com/digitalocean/netbox/issues/1200) - Form validation error when connecting power ports to power outlets
+
+---
+
+v2.0.2 (2017-05-15)
+
+## Enhancements
+
+* [#1122](https://github.com/digitalocean/netbox/issues/1122) - Include NAT inside IPs in IP address list
+* [#1137](https://github.com/digitalocean/netbox/issues/1137) - Allow filtering devices list by rack
+* [#1170](https://github.com/digitalocean/netbox/issues/1170) - Include A and Z sites for circuits in global search results
+* [#1172](https://github.com/digitalocean/netbox/issues/1172) - Linkify racks in side-by-side elevations view
+* [#1177](https://github.com/digitalocean/netbox/issues/1177) - Render planned connections as dashed lines on topology maps
+* [#1179](https://github.com/digitalocean/netbox/issues/1179) - Adjust topology map text color based on node background
+* On all object edit forms, allow filtering the tenant list by tenant group
+
+## Bug Fixes
+
+* [#1158](https://github.com/digitalocean/netbox/issues/1158) - Exception thrown when creating a device component with an invalid name
+* [#1159](https://github.com/digitalocean/netbox/issues/1159) - Only superusers can see "edit IP" buttons on the device interfaces list
+* [#1160](https://github.com/digitalocean/netbox/issues/1160) - Linkify secrets and tenants in global search results
+* [#1161](https://github.com/digitalocean/netbox/issues/1161) - Fix "add another" behavior when creating an API token
+* [#1166](https://github.com/digitalocean/netbox/issues/1166) - Fixed bulk IP address creation when assigning tenants
+* [#1168](https://github.com/digitalocean/netbox/issues/1168) - Total count of objects missing from list view paginator
+* [#1171](https://github.com/digitalocean/netbox/issues/1171) - Allow removing site assignment when bulk editing VLANs
+* [#1173](https://github.com/digitalocean/netbox/issues/1173) - Tweak interface manager to fall back to naive ordering
+
+---
+
+v2.0.1 (2017-05-10)
+
+## Bug Fixes
+
+* [#1149](https://github.com/digitalocean/netbox/issues/1149) - Port list does not populate when creating a console or power connection
+* [#1150](https://github.com/digitalocean/netbox/issues/1150) - Error when uploading image attachments with Unicode names under Python 2
+* [#1151](https://github.com/digitalocean/netbox/issues/1151) - Server error: name 'escape' is not defined
+* [#1152](https://github.com/digitalocean/netbox/issues/1152) - Unable to edit user keys
+* [#1153](https://github.com/digitalocean/netbox/issues/1153) - UnicodeEncodeError when searching for non-ASCII characters on Python 2
+
+---
+
+v2.0.0 (2017-05-09)
+
+## New Features
+
+### API 2.0 ([#113](https://github.com/digitalocean/netbox/issues/113))
+
+The NetBox API has been completely rewritten and now features full read/write ability.
+
+### Image Attachments ([#152](https://github.com/digitalocean/netbox/issues/152))
+
+Users are now able to attach photos and other images to sites, racks, and devices. (Please ensure that the new `media` directory is writable by the system account NetBox runs as.)
+
+### Global Search ([#159](https://github.com/digitalocean/netbox/issues/159))
+
+NetBox now supports searching across all primary object types at once.
+
+### Rack Elevations View ([#951](https://github.com/digitalocean/netbox/issues/951))
+
+A new view has been introduced to display the elevations of multiple racks side-by-side.
+
+## Enhancements
+
+* [#154](https://github.com/digitalocean/netbox/issues/154) - Expanded device status field to include options other than active/offline
+* [#430](https://github.com/digitalocean/netbox/issues/430) - Include circuits when rendering topology maps
+* [#578](https://github.com/digitalocean/netbox/issues/578) - Show topology maps not assigned to a site on the home view
+* [#1100](https://github.com/digitalocean/netbox/issues/1100) - Add a "view all" link to completed bulk import views is_pool for prefixes)
+* [#1110](https://github.com/digitalocean/netbox/issues/1110) - Expand bulk edit forms to include boolean fields (e.g. toggle is_pool for prefixes)
+
+## Bug Fixes
+
+From v1.9.6:
+
+* [#403](https://github.com/digitalocean/netbox/issues/403) - Record console/power/interface connects and disconnects as user actions
+* [#853](https://github.com/digitalocean/netbox/issues/853) -  Added "status" field to device bulk import form
+* [#1101](https://github.com/digitalocean/netbox/issues/1101) - Fix AJAX scripting for device component selection forms
+* [#1103](https://github.com/digitalocean/netbox/issues/1103) - Correct handling of validation errors when creating IP addresses in bulk
+* [#1104](https://github.com/digitalocean/netbox/issues/1104) - Fix VLAN assignment on prefix import
+* [#1115](https://github.com/digitalocean/netbox/issues/1115) - Enabled responsive (side-scrolling) tables for small screens
+* [#1116](https://github.com/digitalocean/netbox/issues/1116) - Correct object links on recursive deletion error
+* [#1125](https://github.com/digitalocean/netbox/issues/1125) - Include MAC addresses on a device's interface list
+* [#1144](https://github.com/digitalocean/netbox/issues/1144) - Allow multiple status selections for Prefix, IP address, and VLAN filters
+
+From beta3:
+
+* [#1113](https://github.com/digitalocean/netbox/issues/1113) - Fixed server error when attempting to delete an image attachment
+* [#1114](https://github.com/digitalocean/netbox/issues/1114) - Suppress OSError when attempting to access a deleted image attachment
+* [#1126](https://github.com/digitalocean/netbox/issues/1126) - Fixed server error when editing a user key via admin UI attachment
+* [#1132](https://github.com/digitalocean/netbox/issues/1132) - Prompt user to unlock session key when importing secrets
+
+## Additional Changes
+
+* The Module DCIM model has been renamed to InventoryItem to better reflect its intended function, and to make room for work on [#824](https://github.com/digitalocean/netbox/issues/824).
+* Redundant portions of the admin UI have been removed ([#973](https://github.com/digitalocean/netbox/issues/973)).
+* The Docker build components have been moved into [their own repository](https://github.com/digitalocean/netbox-docker).
+
+---
+
+v1.9.6 (2017-04-21)
+
+## Improvements
+
+* [#878](https://github.com/digitalocean/netbox/issues/878) - Merged IP addresses with interfaces list on device view
+* [#1001](https://github.com/digitalocean/netbox/issues/1001) - Interface assignment can be modified when editing an IP address
+* [#1084](https://github.com/digitalocean/netbox/issues/1084) - Include custom fields when creating IP addresses in bulk
+
+## Bug Fixes
+
+* [#1057](https://github.com/digitalocean/netbox/issues/1057) - Corrected VLAN validation during prefix import
+* [#1061](https://github.com/digitalocean/netbox/issues/1061) - Fixed potential for script injection via create/edit/delete messages
+* [#1070](https://github.com/digitalocean/netbox/issues/1070) - Corrected installation instructions for Python3 on CentOS/RHEL
+* [#1071](https://github.com/digitalocean/netbox/issues/1071) - Protect assigned circuit termination when an interface is deleted
+* [#1072](https://github.com/digitalocean/netbox/issues/1072) - Order LAG interfaces naturally on bulk interface edit form
+* [#1074](https://github.com/digitalocean/netbox/issues/1074) - Require ncclient 0.5.3 (Python 3 fix)
+* [#1090](https://github.com/digitalocean/netbox/issues/1090) - Improved installation documentation for Python 3
+* [#1092](https://github.com/digitalocean/netbox/issues/1092) - Increase randomness in SECRET_KEY generation tool
+
+---
+
+v1.9.5 (2017-04-06)
+
+## Improvements
+
+* [#1052](https://github.com/digitalocean/netbox/issues/1052) - Added rack reservation list and bulk delete views
+
+## Bug Fixes
+
+* [#1038](https://github.com/digitalocean/netbox/issues/1038) - Suppress upgrading to Django 1.11 (will be supported in v2.0)
+* [#1037](https://github.com/digitalocean/netbox/issues/1037) - Fixed error on VLAN import with duplicate VLAN group names
+* [#1047](https://github.com/digitalocean/netbox/issues/1047) - Correct ordering of numbered subinterfaces
+* [#1051](https://github.com/digitalocean/netbox/issues/1051) - Upgraded django-rest-swagger
+
+---
+
+v1.9.4-r1 (2017-04-04)
+
+## Improvements
+
+* [#362](https://github.com/digitalocean/netbox/issues/362) - Added per_page query parameter to control pagination page length
+
+## Bug Fixes
+
+* [#991](https://github.com/digitalocean/netbox/issues/991) - Correct server error on "create and connect another" interface connection
+* [#1022](https://github.com/digitalocean/netbox/issues/1022) - Record user actions when creating IP addresses in bulk
+* [#1027](https://github.com/digitalocean/netbox/issues/1027) - Fixed nav menu highlighting when BASE_PATH is set
+* [#1034](https://github.com/digitalocean/netbox/issues/1034) - Added migration missing from v1.9.4 release
+
+---
+
+v1.9.3 (2017-03-23)
+
+## Improvements
+
+* [#972](https://github.com/digitalocean/netbox/issues/972) - Add ability to filter connections list by device name
+* [#974](https://github.com/digitalocean/netbox/issues/974) - Added MAC address filter to API interfaces list
+* [#978](https://github.com/digitalocean/netbox/issues/978) - Allow filtering device types by function and subdevice role
+* [#981](https://github.com/digitalocean/netbox/issues/981) - Allow filtering primary objects by a given set of IDs
+* [#983](https://github.com/digitalocean/netbox/issues/983) - Include peer device names when listing circuits in device view
+
+## Bug Fixes
+
+* [#967](https://github.com/digitalocean/netbox/issues/967) - Fix error when assigning a new interface to a LAG
+
+---
+
+v1.9.2 (2017-03-14)
+
+## Bug Fixes
+
+* [#950](https://github.com/digitalocean/netbox/issues/950) - Fix site_id error on child device import
+* [#956](https://github.com/digitalocean/netbox/issues/956) - Correct bug affecting unnamed rackless devices
+* [#957](https://github.com/digitalocean/netbox/issues/957) - Correct device site filter count to include unracked devices
+* [#963](https://github.com/digitalocean/netbox/issues/963) - Fix bug in IPv6 address range expansion
+* [#964](https://github.com/digitalocean/netbox/issues/964) - Fix bug when bulk editing/deleting filtered set of objects
+
+---
+
+v1.9.1 (2017-03-08)
+
+## Improvements
+
+* [#945](https://github.com/digitalocean/netbox/issues/945) - Display the current user in the navigation menu
+* [#946](https://github.com/digitalocean/netbox/issues/946) - Disregard mask length when filtering IP addresses by a parent prefix
+
+## Bug Fixes
+
+* [#941](https://github.com/digitalocean/netbox/issues/941) - Corrected old references to rack.site on Device
+* [#943](https://github.com/digitalocean/netbox/issues/943) - Child prefixes missing on Python 3
+* [#944](https://github.com/digitalocean/netbox/issues/944) - Corrected console and power connection form behavior
+* [#948](https://github.com/digitalocean/netbox/issues/948) - Region name should be hyperlinked to site list
+
+---
+
+v1.9.0-r1 (2017-03-03)
+
+## New Features
+
+### Rack Reservations ([#36](https://github.com/digitalocean/netbox/issues/36))
+
+Users can now reserve an arbitrary number of units within a rack, adding a comment noting their intentions. Reservations do not interfere with installed devices: It is possible to reserve a unit for future use even if it is currently occupied by a device.
+
+### Interface Groups ([#105](https://github.com/digitalocean/netbox/issues/105))
+
+A new Link Aggregation Group (LAG) virtual form factor has been added. Physical interfaces can be assigned to a parent LAG interface to represent a port-channel or similar logical bundling of links.
+
+### Regions ([#164](https://github.com/digitalocean/netbox/issues/164))
+
+A new region model has been introduced to allow for the geographic organization of sites. Regions can be nested recursively to form a hierarchy.
+
+### Rackless Devices ([#198](https://github.com/digitalocean/netbox/issues/198))
+
+Previous releases required each device to be assigned to a particular rack within a site. This requirement has been relaxed so that devices must only be assigned to a site, and may optionally be assigned to a rack.
+
+### Global VLANs ([#235](https://github.com/digitalocean/netbox/issues/235))
+
+Assignment of VLANs and VLAN groups to sites is now optional, allowing for the representation of a VLAN spanning multiple sites.
+
+## Improvements
+
+* [#862](https://github.com/digitalocean/netbox/issues/862) - Show both IPv6 and IPv4 primary IPs in device list
+* [#894](https://github.com/digitalocean/netbox/issues/894) - Expand device name max length to 64 characters
+* [#898](https://github.com/digitalocean/netbox/issues/898) - Expanded circuits list in provider view rack face
+* [#901](https://github.com/digitalocean/netbox/issues/901) - Support for filtering prefixes and IP addresses by mask length
+
+## Bug Fixes
+
+* [#872](https://github.com/digitalocean/netbox/issues/872) - Fixed TypeError on bulk IP address creation (Python 3)
+* [#884](https://github.com/digitalocean/netbox/issues/884) - Preserve selected rack unit when changing a device's rack face
+* [#892](https://github.com/digitalocean/netbox/issues/892) - Restored missing edit/delete buttons when viewing child prefixes and IP addresses from a parent object
+* [#897](https://github.com/digitalocean/netbox/issues/897) - Fixed power connections CSV export
+* [#903](https://github.com/digitalocean/netbox/issues/903) - Only alert on missing critical connections if present in the parent device type
+* [#935](https://github.com/digitalocean/netbox/issues/935) - Fix form validation error when connecting an interface using live search
+* [#937](https://github.com/digitalocean/netbox/issues/937) - Region assignment should be optional when creating a site
+* [#938](https://github.com/digitalocean/netbox/issues/938) - Provider view yields an error if one or more circuits is assigned to a tenant
+
+---
+
+v1.8.4 (2017-02-03)
+
+## Improvements
+
+* [#856](https://github.com/digitalocean/netbox/issues/856) - Strip whitespace from fields during CSV import
+
+## Bug Fixes
+
+* [#851](https://github.com/digitalocean/netbox/issues/851) - Resolve encoding issues during import/export (Python 3)
+* [#854](https://github.com/digitalocean/netbox/issues/854) - Correct processing of get_return_url() in ObjectDeleteView
+* [#859](https://github.com/digitalocean/netbox/issues/859) - Fix Javascript for connection status toggle button on device view
+* [#861](https://github.com/digitalocean/netbox/issues/861) - Avoid overwriting device primary IP assignment from alternate family during bulk import of IP addresses
+* [#865](https://github.com/digitalocean/netbox/issues/865) - Fix server error when attempting to delete a protected object parent (Python 3)
+
+---
+
+v1.8.3 (2017-01-26)
+
+## Improvements
+
+* [#782](https://github.com/digitalocean/netbox/issues/782) - Allow filtering devices list by manufacturer
+* [#820](https://github.com/digitalocean/netbox/issues/820) - Add VLAN column to parent prefixes table on IP address view
+* [#821](https://github.com/digitalocean/netbox/issues/821) - Support for comma separation in bulk IP/interface creation
+* [#827](https://github.com/digitalocean/netbox/issues/827) - **Introduced support for Python 3**
+* [#836](https://github.com/digitalocean/netbox/issues/836) - Add "deprecated" status for IP addresses
+* [#841](https://github.com/digitalocean/netbox/issues/841) - Merged search and filter forms on all object lists
+
+## Bug Fixes
+
+* [#816](https://github.com/digitalocean/netbox/issues/816) - Redirect back to parent prefix view after deleting child prefixes termination
+* [#817](https://github.com/digitalocean/netbox/issues/817) - Update last_updated time of a circuit when editing a child termination
+* [#830](https://github.com/digitalocean/netbox/issues/830) - Redirect user to device view after editing a device component
+* [#840](https://github.com/digitalocean/netbox/issues/840) - Correct API path resolution for secrets when BASE_PATH is configured
+* [#844](https://github.com/digitalocean/netbox/issues/844) - Apply order_naturally() to API interfaces list
+* [#845](https://github.com/digitalocean/netbox/issues/845) - Fix missing edit/delete buttons on object tables for non-superusers
+
+
+---
+
+v1.8.2 (2017-01-18)
+
+## Improvements
+
+* [#284](https://github.com/digitalocean/netbox/issues/284) - Enabled toggling of interface display order per device type
+* [#760](https://github.com/digitalocean/netbox/issues/760) - Redirect user back to device view after deleting an assigned IP address
+* [#783](https://github.com/digitalocean/netbox/issues/783) - Add a description field to the Circuit model
+* [#797](https://github.com/digitalocean/netbox/issues/797) - Add description column to VLANs table
+* [#803](https://github.com/digitalocean/netbox/issues/803) - Clarify that no child objects are deleted when deleting a prefix
+* [#805](https://github.com/digitalocean/netbox/issues/805) - Linkify site column in device table
+
+## Bug Fixes
+
+* [#776](https://github.com/digitalocean/netbox/issues/776) - Prevent circuits from appearing twice while searching
+* [#778](https://github.com/digitalocean/netbox/issues/778) - Corrected an issue preventing multiple interfaces with the same position ID from appearing in a device's interface list
+* [#785](https://github.com/digitalocean/netbox/issues/785) - Trigger validation error when importing a prefix assigned to a nonexistent VLAN
+* [#802](https://github.com/digitalocean/netbox/issues/802) - Fixed enforcement of ENFORCE_GLOBAL_UNIQUE for prefixes
+* [#807](https://github.com/digitalocean/netbox/issues/807) - Redirect user back to form when adding IP addresses in bulk and "create and add another" is clicked
+* [#810](https://github.com/digitalocean/netbox/issues/810) - Suppress unique IP validation on invalid IP addresses and prefixes
+
+---
+
+v1.8.1 (2017-01-04)
+
+## Improvements
+
+* [#771](https://github.com/digitalocean/netbox/issues/771) - Don't automatically redirect user when only one object is returned in a list
+
+## Bug Fixes
+
+* [#764](https://github.com/digitalocean/netbox/issues/764) - Encapsulate in double quotes values containing commas when exporting to CSV
+* [#767](https://github.com/digitalocean/netbox/issues/767) - Fixes xconnect_id error when searching for circuits
+* [#769](https://github.com/digitalocean/netbox/issues/769) - Show default value for boolean custom fields
+* [#772](https://github.com/digitalocean/netbox/issues/772) - Fixes TypeError in API RackUnitListView when no device is excluded
+
+---
+
+v1.8.0 (2017-01-03)
+
+## New Features
+
+### Point-to-Point Circuits ([#49](https://github.com/digitalocean/netbox/issues/49))
+
+Until now, NetBox has supported tracking only one end of a data circuit. This is fine for Internet connections where you don't care (or know) much about the provider side of the circuit, but many users need the ability to track inter-site circuits as well. This release expands circuit modeling so that each circuit can have an A and/or Z side. Each endpoint must be terminated to a site, and may optionally be terminated to a specific device and interface within that site.
+
+### L4 Services ([#539](https://github.com/digitalocean/netbox/issues/539))
+
+Our first major community contribution introduces the ability to track discrete TCP and UDP services associated with a device (for example, SSH or HTTP). Each service can optionally be assigned to one or more specific IP addresses belonging to the device. Thanks to [@if-fi](https://github.com/if-fi) for the addition!
+
+## Improvements
+
+* [#122](https://github.com/digitalocean/netbox/issues/122) - Added comments field to device types
+* [#181](https://github.com/digitalocean/netbox/issues/181) - Implemented support for bulk IP address creation
+* [#613](https://github.com/digitalocean/netbox/issues/613) - Added prefixes column to VLAN list; added VLAN column to prefix list
+* [#716](https://github.com/digitalocean/netbox/issues/716) - Add ASN field to site bulk edit form
+* [#722](https://github.com/digitalocean/netbox/issues/722) - Enabled custom fields for device types
+* [#743](https://github.com/digitalocean/netbox/issues/743) - Enabled bulk creation of all device components
+* [#756](https://github.com/digitalocean/netbox/issues/756) - Added contact details to site model
+
+## Bug Fixes
+
+* [#563](https://github.com/digitalocean/netbox/issues/563) - Allow a device to be flipped from one rack face to the other without moving it
+* [#658](https://github.com/digitalocean/netbox/issues/658) - Enabled conditional treatment of network/broadcast IPs for a prefix by defining it as a pool
+* [#741](https://github.com/digitalocean/netbox/issues/741) - Hide "select all" button for users without edit permissions
+* [#744](https://github.com/digitalocean/netbox/issues/744) - Fixed export of sites without an AS number
+* [#747](https://github.com/digitalocean/netbox/issues/747) - Fixed natural_order_by integer cast error on large numbers
+* [#751](https://github.com/digitalocean/netbox/issues/751) - Fixed python-cryptography installation issue on Debian
+* [#763](https://github.com/digitalocean/netbox/issues/763) - Added missing fields to CSV exports for racks and prefixes
+
+---
+
+v1.7.3 (2016-12-08)
+
+## Bug Fixes
+
+* [#724](https://github.com/digitalocean/netbox/issues/724) - Exempt API views from LoginRequiredMiddleware to enable basic HTTP authentication when LOGIN_REQUIRED is true
+* [#729](https://github.com/digitalocean/netbox/issues/729) - Corrected cancellation links when editing secondary objects
+* [#732](https://github.com/digitalocean/netbox/issues/732) - Allow custom select field values to be deselected if the field is not required
+* [#733](https://github.com/digitalocean/netbox/issues/733) - Fixed MAC address filter on device list
+* [#734](https://github.com/digitalocean/netbox/issues/734) - Corrected display of device type when editing a device
+
+---
+
+v1.7.2-r1 (2016-12-06)
+
+## Improvements
+
+* [#663](https://github.com/digitalocean/netbox/issues/663) - Added MAC address search field to device list
+* [#672](https://github.com/digitalocean/netbox/issues/672) - Increased the selection of available colors for rack and device roles
+* [#695](https://github.com/digitalocean/netbox/issues/695) - Added is_private field to RIR
+
+## Bug Fixes
+
+* [#677](https://github.com/digitalocean/netbox/issues/677) - Fix setuptools installation error on Debian 8.6
+* [#696](https://github.com/digitalocean/netbox/issues/696) - Corrected link to VRF in prefix and IP address breadcrumbs
+* [#702](https://github.com/digitalocean/netbox/issues/702) - Improved Unicode support for custom fields
+* [#712](https://github.com/digitalocean/netbox/issues/712) - Corrected export of tenants which are not assigned to a group
+* [#713](https://github.com/digitalocean/netbox/issues/713) - Include a label for the comments field when editing circuits, providers, or racks in bulk
+* [#718](https://github.com/digitalocean/netbox/issues/718) - Restore is_primary field on IP assignment form
+* [#723](https://github.com/digitalocean/netbox/issues/723) - API documentation is now accessible when using BASE_PATH
+* [#727](https://github.com/digitalocean/netbox/issues/727) - Corrected error in rack elevation display (v1.7.2)
+
+---
+
+v1.7.1 (2016-11-15)
+
+## Improvements
+
+* [#667](https://github.com/digitalocean/netbox/issues/667) - Added prefix utilization statistics to the RIR list view
+* [#685](https://github.com/digitalocean/netbox/issues/685) - When assigning an IP to a device, automatically select the interface if only one exists
+
+## Bug Fixes
+
+* [#674](https://github.com/digitalocean/netbox/issues/674) - Fix assignment of status to imported IP addresses
+* [#676](https://github.com/digitalocean/netbox/issues/676) - Server error when bulk editing device types
+* [#678](https://github.com/digitalocean/netbox/issues/678) - Server error on device import specifying an invalid device type
+* [#691](https://github.com/digitalocean/netbox/issues/691) - Allow the assignment of power ports to PDUs
+* [#692](https://github.com/digitalocean/netbox/issues/692) - Form errors are not displayed on checkbox fields
+
+---
+
+v1.7.0 (2016-11-03)
+
+## New Features
+
+### IP address statuses ([#87](https://github.com/digitalocean/netbox/issues/87))
+
+An IP address can now be designated as active, reserved, or DHCP. The DHCP status implies that the IP address is part of a DHCP pool and may or may not be assigned to a DHCP client.
+
+### Top-to-bottom rack numbering ([#191](https://github.com/digitalocean/netbox/issues/191))
+
+Racks can now be set to have descending rack units, with U1 at the top of the rack. When adding a device to a rack with descending units, be sure to position it in the **lowest-numbered** unit which it occupies (this will be physically the topmost unit).
+
+## Improvements
+* [#211](https://github.com/digitalocean/netbox/issues/211) - Allow device assignment and removal from IP address view
+* [#630](https://github.com/digitalocean/netbox/issues/630) - Added a custom 404 page
+* [#652](https://github.com/digitalocean/netbox/issues/652) - Use password input controls when editing secrets
+* [#654](https://github.com/digitalocean/netbox/issues/654) - Added Cisco FlexStack and FlexStack Plus form factors
+* [#661](https://github.com/digitalocean/netbox/issues/661) - Display relevant IP addressing when viewing a circuit
+
+## Bug Fixes
+* [#632](https://github.com/digitalocean/netbox/issues/632) - Use semicolons instead of commas to separate regexes in topology maps
+* [#647](https://github.com/digitalocean/netbox/issues/647) - Extend form used when assigning an IP to a device
+* [#657](https://github.com/digitalocean/netbox/issues/657) - Unicode error when adding device modules
+* [#660](https://github.com/digitalocean/netbox/issues/660) - Corrected calculation of utilized space in rack list
+* [#664](https://github.com/digitalocean/netbox/issues/664) - Fixed bulk creation of interfaces across multiple devices
+
+---
+
+v1.6.3 (2016-10-19)
+
+## Improvements
+
+* [#353](https://github.com/digitalocean/netbox/issues/353) - Bulk editing of device and device type interfaces
+* [#527](https://github.com/digitalocean/netbox/issues/527) - Support for nullification of fields when bulk editing
+* [#592](https://github.com/digitalocean/netbox/issues/592) - Allow space-delimited lists of ALLOWED_HOSTS in Docker
+* [#608](https://github.com/digitalocean/netbox/issues/608) - Added "select all" button for device and device type components
+
+## Bug Fixes
+
+* [#602](https://github.com/digitalocean/netbox/issues/602) - Correct display of custom integer fields with value of 0 or 1
+* [#604](https://github.com/digitalocean/netbox/issues/604) - Correct display of unnamed devices in form selection fields
+* [#611](https://github.com/digitalocean/netbox/issues/611) - Power/console/interface connection import: status field should be case-insensitive
+* [#615](https://github.com/digitalocean/netbox/issues/615) - Account for BASE_PATH in static URLs and during login
+* [#616](https://github.com/digitalocean/netbox/issues/616) - Correct display of custom URL fields
+
+---
+
+v1.6.2-r1 (2016-10-04)
+
+## Improvements
+
+* [#212](https://github.com/digitalocean/netbox/issues/212) - Introduced the `BASE_PATH` configuration setting to allow running NetBox in a URL subdirectory
+* [#345](https://github.com/digitalocean/netbox/issues/345) - Bulk edit: allow user to select all objects on page or all matching query
+* [#475](https://github.com/digitalocean/netbox/issues/475) - Display "add" buttons at top and bottom of all device/device type panels
+* [#480](https://github.com/digitalocean/netbox/issues/480) - Improved layout on mobile devices
+* [#481](https://github.com/digitalocean/netbox/issues/481) - Require interface creation before trying to assign an IP to a device
+* [#575](https://github.com/digitalocean/netbox/issues/575) - Allow all valid URL schemes in custom fields
+* [#579](https://github.com/digitalocean/netbox/issues/579) - Add a description field to export templates
+
+## Bug Fixes
+
+* [#466](https://github.com/digitalocean/netbox/issues/466) - Validate available free space for all instances when increasing the U height of a device type
+* [#571](https://github.com/digitalocean/netbox/issues/571) - Correct rack group filter on device list
+* [#576](https://github.com/digitalocean/netbox/issues/576) - Delete all relevant CustomFieldValues when deleting a CustomFieldChoice
+* [#581](https://github.com/digitalocean/netbox/issues/581) - Correct initialization of custom boolean and select fields
+* [#591](https://github.com/digitalocean/netbox/issues/591) - Correct display of component creation buttons in device type view
+
+---
+
+v1.6.1-r1 (2016-09-21)
+
+## Improvements
+* [#415](https://github.com/digitalocean/netbox/issues/415) - Add an expand/collapse toggle button to the prefix list
+* [#552](https://github.com/digitalocean/netbox/issues/552) - Allow filtering on custom select fields by "none"
+* [#561](https://github.com/digitalocean/netbox/issues/561) - Make custom fields accessible from within export templates
+
+## Bug Fixes
+* [#493](https://github.com/digitalocean/netbox/issues/493) - CSV import support for UTF-8
+* [#531](https://github.com/digitalocean/netbox/issues/531) - Order prefix list by VRF assignment
+* [#542](https://github.com/digitalocean/netbox/issues/542) - Add LDAP support in Docker
+* [#557](https://github.com/digitalocean/netbox/issues/557) - Add 'global' choice to VRF filter for prefixes and IP addresses
+* [#558](https://github.com/digitalocean/netbox/issues/558) - Update slug field when name is populated without a key press
+* [#562](https://github.com/digitalocean/netbox/issues/562) - Fixed bulk interface creation
+* [#564](https://github.com/digitalocean/netbox/issues/564) - Display custom fields for all applicable objects
+
+---
+
+v1.6.0 (2016-09-13)
+
+## New Features
+
+### Custom Fields ([#129](https://github.com/digitalocean/netbox/issues/129))
+
+Users can now create custom fields to associate arbitrary data with core NetBox objects. For example, you might want to add a geolocation tag to IP prefixes, or a ticket number to each device. Text, integer, boolean, date, URL, and selection fields are supported.
+
+## Improvements
+
+* [#489](https://github.com/digitalocean/netbox/issues/489) - Docker file now builds from a `python:2.7-wheezy` base instead of `ubuntu:14.04`
+* [#540](https://github.com/digitalocean/netbox/issues/540) - Add links for VLAN roles under VLAN navigation menu
+* Added new interface form factors
+* Added address family filters to aggregate and prefix lists
+
+## Bug Fixes
+
+* [#476](https://github.com/digitalocean/netbox/issues/476) - Corrected rack import instructions
+* [#484](https://github.com/digitalocean/netbox/issues/484) - Allow bulk deletion of >1K objects
+* [#486](https://github.com/digitalocean/netbox/issues/486) - Prompt for secret key only if updating a secret's value
+* [#490](https://github.com/digitalocean/netbox/issues/490) - Corrected display of circuit commit rate
+* [#495](https://github.com/digitalocean/netbox/issues/495) - Include tenant in prefix and IP CSV export
+* [#507](https://github.com/digitalocean/netbox/issues/507) - Corrected rendering of nav menu on screens narrower than 1200px
+* [#515](https://github.com/digitalocean/netbox/issues/515) - Clarified instructions for the "face" field when importing devices
+* [#522](https://github.com/digitalocean/netbox/issues/522) - Remove obsolete check for staff status when bulk deleting objects
+* [#544](https://github.com/digitalocean/netbox/issues/544) - Strip CRLF-style line terminators from rendered export templates
+
+---
+
+v1.5.2 (2016-08-16)
+
+## Bug Fixes
+
+* [#460](https://github.com/digitalocean/netbox/issues/460) - Corrected ordering of IP addresses with differing prefix lengths
+* [#463](https://github.com/digitalocean/netbox/issues/463) - Prevent pre-population of livesearch field with '---------'
+* [#467](https://github.com/digitalocean/netbox/issues/467) - Include prefixes and IPs which inherit tenancy from their VRF in tenant stats
+* [#468](https://github.com/digitalocean/netbox/issues/468) - Don't allow connected interfaces to be changed to the "virtual" form factor
+* [#469](https://github.com/digitalocean/netbox/issues/469) - Added missing import buttons to list views
+* [#472](https://github.com/digitalocean/netbox/issues/472) - Hide the connection button for interfaces which have a circuit terminated to them
+
+---
+
+v1.5.1 (2016-08-11)
+
+## Improvements
+
+* [#421](https://github.com/digitalocean/netbox/issues/421) - Added an asset tag field to devices
+* [#456](https://github.com/digitalocean/netbox/issues/456) - Added IP search box to home page
+* Colorized rack and device roles
+
+## Bug Fixes
+
+* [#454](https://github.com/digitalocean/netbox/issues/454) - Corrected error on rack export
+* [#457](https://github.com/digitalocean/netbox/issues/457) - Added role field to rack edit form
+
+---
+
+v1.5.0 (2016-08-10)
+
+## New Features
+
+### Rack Enhancements ([#180](https://github.com/digitalocean/netbox/issues/180), [#241](https://github.com/digitalocean/netbox/issues/241))
+
+Like devices, racks can now be assigned to functional roles. This allows users to group racks by designated function as well as by physical location (rack groups). Additionally, rack can now have a defined rail-to-rail width (19 or 23 inches) and a type (two-post-rack, cabinet, etc.).
+
+## Improvements
+
+* [#149](https://github.com/digitalocean/netbox/issues/149) - Added discrete upstream speed field for circuits
+* [#157](https://github.com/digitalocean/netbox/issues/157) - Added manufacturer field for device modules
+* We have a logo!
+* Upgraded to Django 1.10
+
+## Bug Fixes
+
+* [#433](https://github.com/digitalocean/netbox/issues/433) - Corrected form validation when editing child devices
+* [#442](https://github.com/digitalocean/netbox/issues/442) - Corrected child device import instructions
+* [#443](https://github.com/digitalocean/netbox/issues/443) - Correctly display and initialize VRF for creation of new IP addresses
+* [#444](https://github.com/digitalocean/netbox/issues/444) - Corrected prefix model validation
+* [#445](https://github.com/digitalocean/netbox/issues/445) - Limit rack height to between 1U and 100U (inclusive)
+
+---
+
+v1.4.2 (2016-08-06)
+
+## Improvements
+
+* [#167](https://github.com/digitalocean/netbox/issues/167) - Added new interface form factors
+* [#253](https://github.com/digitalocean/netbox/issues/253) - Added new interface form factors
+* [#434](https://github.com/digitalocean/netbox/issues/434) - Restored admin UI access to user action history (however bulk deletion is disabled)
+* [#435](https://github.com/digitalocean/netbox/issues/435) - Added an "add prefix" button to the VLAN view
+
+## Bug Fixes
+
+* [#425](https://github.com/digitalocean/netbox/issues/425) - Ignore leading and trailing periods when generating a slug
+* [#427](https://github.com/digitalocean/netbox/issues/427) - Prevent error when duplicate IPs are present in a prefix's IP list
+* [#429](https://github.com/digitalocean/netbox/issues/429) - Correct redirection of user when adding a secret to a device
+
+---
+
+v1.4.1 (2016-08-03)
+
+## Improvements
+
+* [#289](https://github.com/digitalocean/netbox/issues/289) - Annotate available ranges in prefix IP list
+* [#412](https://github.com/digitalocean/netbox/issues/412) - Tenant group assignment is no longer mandatory
+* [#422](https://github.com/digitalocean/netbox/issues/422) - CSV import now supports double-quoting values which contain commas
+
+## Bug Fixes
+
+* [#395](https://github.com/digitalocean/netbox/issues/395) - Show child prefixes from all VRFs if the parent belongs to the global table
+* [#406](https://github.com/digitalocean/netbox/issues/406) - Fixed circuit list rendring when filtering on port speed or commit rate
+* [#409](https://github.com/digitalocean/netbox/issues/409) - Filter IPs and prefixes by tenant slug rather than by its PK
+* [#411](https://github.com/digitalocean/netbox/issues/411) - Corrected title of secret roles view
+* [#419](https://github.com/digitalocean/netbox/issues/419) - Fixed a potential database performance issue when gathering tenant statistics
+
+---
+
+v1.4.0 (2016-08-01)
+
+## New Features
+
+### Multitenancy ([#16](https://github.com/digitalocean/netbox/issues/16))
+
+NetBox now supports tenants and tenant groups. Sites, racks, devices, VRFs, prefixes, IP addresses, VLANs, and circuits can be assigned to tenants to track the allocation of these resources among customers or internal departments. If a prefix or IP address does not have a tenant assigned, it will fall back to the tenant assigned to its parent VRF (where applicable).
+
+## Improvements
+
+* [#176](https://github.com/digitalocean/netbox/issues/176) - Introduced seed data for new installs
+* [#358](https://github.com/digitalocean/netbox/issues/358) - Improved search for all objects
+* [#394](https://github.com/digitalocean/netbox/issues/394) - Improved VRF selection during bulk editing of prefixes and IP addresses
+* Miscellaneous cosmetic improvements to the UI
+
+## Bug Fixes
+
+* [#392](https://github.com/digitalocean/netbox/issues/392) - Don't include child devices in non-racked devices table
+* [#397](https://github.com/digitalocean/netbox/issues/397) - Only include child IPs which belong to the same VRF as the parent prefix
+
+---
+
+v1.3.2 (2016-07-26)
+
+## Improvements
+
+* [#292](https://github.com/digitalocean/netbox/issues/292) - Added part_number field to DeviceType
+* [#363](https://github.com/digitalocean/netbox/issues/363) - Added a description field to the VLAN model
+* [#374](https://github.com/digitalocean/netbox/issues/374) - Increased VLAN name length to 64 characters
+* Enabled bulk deletion of interfaces from devices
+
+## Bug Fixes
+
+* [#359](https://github.com/digitalocean/netbox/issues/359) - Corrected the DCIM API endpoint for finding related connections
+* [#370](https://github.com/digitalocean/netbox/issues/370) - Notify user when secret decryption fails
+* [#381](https://github.com/digitalocean/netbox/issues/381) - Fix 'u_consumed' error on rack import
+* [#384](https://github.com/digitalocean/netbox/issues/384) - Fixed description field's maximum length on IPAM bulk edit forms
+* [#385](https://github.com/digitalocean/netbox/issues/385) - Fixed error when deleting a user with one or more associated UserActions
+
+---
+
+v1.3.1 (2016-07-21)
+
+## Improvements
+
+* [#258](https://github.com/digitalocean/netbox/issues/258) - Add an API endpoint to list interface connections
+* [#303](https://github.com/digitalocean/netbox/issues/303) - Improved numeric ordering of sites, racks, and devices
+* [#304](https://github.com/digitalocean/netbox/issues/304) - Display utilization percentage on rack list
+* [#327](https://github.com/digitalocean/netbox/issues/327) - Disable rack assignment for installed child devices
+
+## Bug Fixes
+
+* [#331](https://github.com/digitalocean/netbox/issues/331) - Add group field to VLAN bulk edit form
+* Miscellaneous improvements to Unicode handling
+
+---
+
+v1.3.0 (2016-07-18)
+
+## New Features
+
+* [#42](https://github.com/digitalocean/netbox/issues/42) - Allow assignment of VLAN on prefix import
+* [#43](https://github.com/digitalocean/netbox/issues/43) - Toggling of IP space uniqueness within a VRF
+* [#111](https://github.com/digitalocean/netbox/issues/111) - Introduces VLAN groups
+* [#227](https://github.com/digitalocean/netbox/issues/227) - Support for bulk import of child devices
+
+## Bug Fixes
+
+* [#301](https://github.com/digitalocean/netbox/issues/301) - Prevent deletion of DeviceBay when installed device is deleted
+* [#306](https://github.com/digitalocean/netbox/issues/306) - Fixed device import to allow an unspecified rack face
+* [#307](https://github.com/digitalocean/netbox/issues/307) - Catch `RelatedObjectDoesNotExist` when an invalid device type is defined during device import
+* [#308](https://github.com/digitalocean/netbox/issues/308) - Update rack assignment for all child devices when moving a parent device
+* [#311](https://github.com/digitalocean/netbox/issues/311) - Fix assignment of primary_ip on IP address import
+* [#317](https://github.com/digitalocean/netbox/issues/317) - Rack elevation display fix for device types greater than 42U in height
+* [#320](https://github.com/digitalocean/netbox/issues/320) - Disallow import of prefixes with host masks
+* [#322](https://github.com/digitalocean/netbox/issues/320) - Corrected VLAN import behavior
+
+---
+
+v1.2.2 (2016-07-14)
+
+## Improvements
+
+* [#174](https://github.com/digitalocean/netbox/issues/174) - Added search and site filter to provider list
+* [#270](https://github.com/digitalocean/netbox/issues/270) - Added the ability to filter devices by rack group
+
+## Bug Fixes
+
+* [#115](https://github.com/digitalocean/netbox/issues/115) - Fix deprecated django.core.context_processors reference
+* [#268](https://github.com/digitalocean/netbox/issues/268) - Added support for entire 32-bit ASN space
+* [#282](https://github.com/digitalocean/netbox/issues/282) - De-select "all" checkbox if one or more objects are deselected
+* [#290](https://github.com/digitalocean/netbox/issues/290) - Always display management interfaces for a device type (even if `is_network_device` is not set)
+
+---
+
+v1.2.1 (2016-07-13)
+
+**Note:** This release introduces a new dependency ([natsort](https://pypi.python.org/pypi/natsort)). Be sure to run `upgrade.sh` if upgrading from a previous release.
+
+## Improvements
+
+* [#285](https://github.com/digitalocean/netbox/issues/285) - Added the ability to prefer IPv4 over IPv6 for primary device IPs
+
+## Bug Fixes
+
+* [#243](https://github.com/digitalocean/netbox/issues/243) - Improved ordering of device object lists
+* [#271](https://github.com/digitalocean/netbox/issues/271) - Fixed primary_ip bug in secrets API
+* [#274](https://github.com/digitalocean/netbox/issues/274) - Fixed primary_ip bug in DCIM admin UI
+* [#275](https://github.com/digitalocean/netbox/issues/275) - Fixed bug preventing the expansion of an existing aggregate
+
+---
+
+v1.2.0 (2016-07-12)
+
+## New Features
+
+* [#73](https://github.com/digitalocean/netbox/issues/73) - Added optional persistent banner
+* [#93](https://github.com/digitalocean/netbox/issues/73) - Ability to set both IPv4 and IPv6 primary IPs for devices
+* [#203](https://github.com/digitalocean/netbox/issues/203) - Introduced support for LDAP
+
+## Bug Fixes
+
+* [#162](https://github.com/digitalocean/netbox/issues/228) - Fixed support for Unicode characters in rack/device/VLAN names
+* [#228](https://github.com/digitalocean/netbox/issues/228) - Corrected conditional inclusion of device bay templates
+* [#246](https://github.com/digitalocean/netbox/issues/246) - Corrected Docker build instructions
+* [#260](https://github.com/digitalocean/netbox/issues/260) - Fixed error on admin UI device type list
+* Miscellaneous layout improvements for mobile devices
+
+---
+
+v1.1.0 (2016-07-07)
+
+## New Features
+
+* [#107](https://github.com/digitalocean/netbox/pull/107) - Docker support
+* [#91](https://github.com/digitalocean/netbox/issues/91) - Support for subdevices within a device
+* [#170](https://github.com/digitalocean/netbox/pull/170) - Added MAC address field to interfaces
+
+## Bug Fixes
+
+* [#169](https://github.com/digitalocean/netbox/issues/169) - Fix rendering of cancellation URL when editing objects
+* [#183](https://github.com/digitalocean/netbox/issues/183) - Ignore vi swap files
+* [#209](https://github.com/digitalocean/netbox/issues/209) - Corrected error when not confirming component template deletions
+* [#214](https://github.com/digitalocean/netbox/issues/214) - Fixed redundant message on bulk interface creation
+* [#68](https://github.com/digitalocean/netbox/issues/68) - Improved permissions-related error reporting for secrets
+
+---
+
+v1.0.7-r1 (2016-07-05)
+
+* [#199](https://github.com/digitalocean/netbox/issues/199) - Correct IP address validation
+
+---
+
+v1.0.7 (2016-06-30)
+
+**Note:** If upgrading from a previous release, be sure to run ./upgrade.sh after downloading the new code.
+* [#135](https://github.com/digitalocean/netbox/issues/135): Fixed display of navigation menu on mobile screens
+* [#141](https://github.com/digitalocean/netbox/issues/141): Fixed rendering of "getting started" guide
+* Modified upgrade.sh to use sudo for pip installations
+* [#109](https://github.com/digitalocean/netbox/issues/109): Hide the navigation menu from anonymous users if login is required
+* [#143](https://github.com/digitalocean/netbox/issues/143): Add help_text to Device.position
+* [#136](https://github.com/digitalocean/netbox/issues/136): Prefixes which have host bits set will trigger an error instead of being silently corrected
+* [#140](https://github.com/digitalocean/netbox/issues/140): Improved support for Unicode in object names
+
+---
+
+1.0.0 (2016-06-27)
+
+NetBox was originally developed internally at DigitalOcean by the network development team. This release marks the debut of NetBox as an open source project.

+ 62 - 28
netbox/circuits/forms.py

@@ -1,5 +1,4 @@
 from django import forms
-from django.db.models import Count
 from taggit.forms import TagField
 
 from dcim.models import Site
@@ -7,8 +6,8 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
 from utilities.forms import (
-    AnnotatedMultipleChoiceField, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, FilterChoiceField,
-    SmallTextarea, SlugField,
+    APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
+    FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
 )
 from .constants import CIRCUIT_STATUS_CHOICES
 from .models import Circuit, CircuitTermination, CircuitType, Provider
@@ -107,7 +106,11 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
     )
     site = FilterChoiceField(
         queryset=Site.objects.all(),
-        to_field_name='slug'
+        to_field_name='slug',
+        widget=APISelect(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+        )
     )
     asn = forms.IntegerField(
         required=False,
@@ -161,6 +164,16 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             'install_date': "Format: YYYY-MM-DD",
             'commit_rate': "Committed rate",
         }
+        widgets = {
+            'provider': APISelect(
+                api_url="/api/circuits/providers/"
+            ),
+            'type': APISelect(
+                api_url="/api/circuits/circuit-types/"
+            ),
+            'status': StaticSelect2(),
+
+        }
 
 
 class CircuitCSVForm(forms.ModelForm):
@@ -209,20 +222,30 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
     )
     type = forms.ModelChoiceField(
         queryset=CircuitType.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/circuits/circuit-types/"
+        )
     )
     provider = forms.ModelChoiceField(
         queryset=Provider.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/circuits/providers/"
+        )
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(CIRCUIT_STATUS_CHOICES),
         required=False,
-        initial=''
+        initial='',
+        widget=StaticSelect2()
     )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/tenancy/tenants/"
+        )
     )
     commit_rate = forms.IntegerField(
         required=False,
@@ -249,35 +272,43 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
         label='Search'
     )
     type = FilterChoiceField(
-        queryset=CircuitType.objects.annotate(
-            filter_count=Count('circuits')
-        ),
-        to_field_name='slug'
+        queryset=CircuitType.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/circuits/circuit-types/",
+            value_field="slug",
+        )
     )
     provider = FilterChoiceField(
-        queryset=Provider.objects.annotate(
-            filter_count=Count('circuits')
-        ),
-        to_field_name='slug'
+        queryset=Provider.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/circuits/providers/",
+            value_field="slug",
+        )
     )
-    status = AnnotatedMultipleChoiceField(
+    status = forms.MultipleChoiceField(
         choices=CIRCUIT_STATUS_CHOICES,
-        annotate=Circuit.objects.all(),
-        annotate_field='status',
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(
-            filter_count=Count('circuits')
-        ),
+        queryset=Tenant.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(
-            filter_count=Count('circuit_terminations')
-        ),
-        to_field_name='slug'
+        queryset=Site.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+        )
     )
     commit_rate = forms.IntegerField(
         required=False,
@@ -304,4 +335,7 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
         }
         widgets = {
             'term_side': forms.HiddenInput(),
+            'site': APISelect(
+                api_url="/api/dcim/sites/"
+            )
         }

+ 11 - 0
netbox/dcim/filters.py

@@ -760,6 +760,10 @@ class InterfaceFilter(django_filters.FilterSet):
     """
     Not using DeviceComponentFilterSet for Interfaces because we need to check for VirtualChassis membership.
     """
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
     device = django_filters.CharFilter(
         method='filter_device',
         field_name='name',
@@ -806,6 +810,13 @@ class InterfaceFilter(django_filters.FilterSet):
         model = Interface
         fields = ['name', 'connection_status', 'form_factor', 'enabled', 'mtu', 'mgmt_only']
 
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(name__icontains=value)
+        ).distinct()
+
     def filter_device(self, queryset, name, value):
         try:
             device = Device.objects.get(**{name: value})

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 320 - 157
netbox/dcim/forms.py


+ 256 - 129
netbox/ipam/forms.py

@@ -1,7 +1,6 @@
 from django import forms
 from django.core.exceptions import MultipleObjectsReturned
 from django.core.validators import MaxValueValidator, MinValueValidator
-from django.db.models import Count
 from taggit.forms import TagField
 
 from dcim.models import Site, Rack, Device, Interface
@@ -9,9 +8,9 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
 from utilities.forms import (
-    AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
-    CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, ReturnURLForm,
-    SlugField, add_blank_choice,
+    add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
+    CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField,
+    StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
 )
 from virtualization.models import VirtualMachine
 from .constants import (
@@ -77,7 +76,10 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
     )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/tenancy/tenants/"
+        )
     )
     enforce_unique = forms.NullBooleanField(
         required=False,
@@ -102,11 +104,14 @@ class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
         label='Search'
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(
-            filter_count=Count('vrfs')
-        ),
+        queryset=Tenant.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
     )
 
 
@@ -139,12 +144,8 @@ class RIRFilterForm(BootstrapMixin, forms.Form):
     is_private = forms.NullBooleanField(
         required=False,
         label='Private',
-        widget=forms.Select(
-            choices=[
-                ('', '---------'),
-                ('True', 'Yes'),
-                ('False', 'No'),
-            ]
+        widget=StaticSelect2(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
         )
     )
 
@@ -168,6 +169,11 @@ class AggregateForm(BootstrapMixin, CustomFieldForm):
             'rir': "Regional Internet Registry responsible for this prefix",
             'date_added': "Format: YYYY-MM-DD",
         }
+        widgets = {
+            'rir': APISelect(
+                api_url="/api/ipam/rirs/"
+            )
+        }
 
 
 class AggregateCSVForm(forms.ModelForm):
@@ -193,7 +199,10 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     rir = forms.ModelChoiceField(
         queryset=RIR.objects.all(),
         required=False,
-        label='RIR'
+        label='RIR',
+        widget=APISelect(
+            api_url="/api/ipam/rirs/"
+        )
     )
     date_added = forms.DateField(
         required=False
@@ -218,12 +227,17 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
     family = forms.ChoiceField(
         required=False,
         choices=IP_FAMILY_CHOICES,
-        label='Address family'
+        label='Address family',
+        widget=StaticSelect2()
     )
     rir = FilterChoiceField(
-        queryset=RIR.objects.annotate(filter_count=Count('aggregates')),
+        queryset=RIR.objects.all(),
         to_field_name='slug',
-        label='RIR'
+        label='RIR',
+        widget=APISelectMultiple(
+            api_url="/api/ipam/rirs/",
+            value_field="slug",
+        )
     )
 
 
@@ -261,9 +275,13 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         queryset=Site.objects.all(),
         required=False,
         label='Site',
-        widget=forms.Select(
+        widget=APISelect(
+            api_url="/api/dcim/sites/",
+            filter_for={
+                'vlan_group': 'site_id',
+                'vlan': 'site_id',
+            },
             attrs={
-                'filter-for': 'vlan_group',
                 'nullable': 'true',
             }
         )
@@ -276,9 +294,11 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         required=False,
         label='VLAN group',
         widget=APISelect(
-            api_url='/api/ipam/vlan-groups/?site_id={{site}}',
+            api_url='/api/ipam/vlan-groups/',
+            filter_for={
+                'vlan': 'group_id'
+            },
             attrs={
-                'filter-for': 'vlan',
                 'nullable': 'true',
             }
         )
@@ -292,7 +312,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         required=False,
         label='VLAN',
         widget=APISelect(
-            api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}',
+            api_url='/api/ipam/vlans/',
             display_field='display_name'
         )
     )
@@ -304,6 +324,15 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant',
             'tags',
         ]
+        widgets = {
+            'vrf': APISelect(
+                api_url="/api/ipam/vrfs/"
+            ),
+            'status': StaticSelect2(),
+            'role': APISelect(
+                api_url="/api/ipam/roles/"
+            )
+        }
 
     def __init__(self, *args, **kwargs):
 
@@ -415,12 +444,18 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/dcim/sites/"
+        )
     )
     vrf = forms.ModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF'
+        label='VRF',
+        widget=APISelect(
+            api_url="/api/ipam/vrfs/"
+        )
     )
     prefix_length = forms.IntegerField(
         min_value=1,
@@ -429,15 +464,22 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/tenancy/tenants/"
+        )
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(PREFIX_STATUS_CHOICES),
-        required=False
+        required=False,
+        widget=StaticSelect2()
     )
     role = forms.ModelChoiceField(
         queryset=Role.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/ipam/roles/"
+        )
     )
     is_pool = forms.NullBooleanField(
         required=False,
@@ -473,47 +515,60 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
     family = forms.ChoiceField(
         required=False,
         choices=IP_FAMILY_CHOICES,
-        label='Address family'
+        label='Address family',
+        widget=StaticSelect2()
     )
     mask_length = forms.ChoiceField(
         required=False,
         choices=PREFIX_MASK_LENGTH_CHOICES,
-        label='Mask length'
+        label='Mask length',
+        widget=StaticSelect2()
     )
     vrf = FilterChoiceField(
-        queryset=VRF.objects.annotate(
-            filter_count=Count('prefixes')
-        ),
+        queryset=VRF.objects.all(),
         to_field_name='rd',
         label='VRF',
-        null_label='-- Global --'
+        null_label='-- Global --',
+        widget=APISelectMultiple(
+            api_url="/api/ipam/vrfs/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(
-            filter_count=Count('prefixes')
-        ),
+        queryset=Tenant.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
     )
-    status = AnnotatedMultipleChoiceField(
+    status = forms.MultipleChoiceField(
         choices=PREFIX_STATUS_CHOICES,
-        annotate=Prefix.objects.all(),
-        annotate_field='status',
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(
-            filter_count=Count('prefixes')
-        ),
+        queryset=Site.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     role = FilterChoiceField(
-        queryset=Role.objects.annotate(
-            filter_count=Count('prefixes')
-        ),
+        queryset=Role.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/ipam/roles/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     expand = forms.BooleanField(
         required=False,
@@ -534,9 +589,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         queryset=Site.objects.all(),
         required=False,
         label='Site',
-        widget=forms.Select(
-            attrs={
-                'filter-for': 'nat_rack'
+        widget=APISelect(
+            api_url="/api/dcim/sites/",
+            filter_for={
+                'nat_rack': 'site_id',
+                'nat_device': 'site_id'
             }
         )
     )
@@ -548,10 +605,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         required=False,
         label='Rack',
         widget=APISelect(
-            api_url='/api/dcim/racks/?site_id={{nat_site}}',
+            api_url='/api/dcim/racks/',
             display_field='display_name',
+            filter_for={
+                'nat_device': 'rack_id'
+            },
             attrs={
-                'filter-for': 'nat_device',
                 'nullable': 'true'
             }
         )
@@ -565,9 +624,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         required=False,
         label='Device',
         widget=APISelect(
-            api_url='/api/dcim/devices/?site_id={{nat_site}}&rack_id={{nat_rack}}',
+            api_url='/api/dcim/devices/',
             display_field='display_name',
-            attrs={'filter-for': 'nat_inside'}
+            filter_for={
+                'nat_inside': 'device_id'
+            }
         )
     )
     nat_inside = ChainedModelChoiceField(
@@ -578,20 +639,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         required=False,
         label='IP Address',
         widget=APISelect(
-            api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}',
+            api_url='/api/ipam/ip-addresses/',
             display_field='address'
         )
     )
-    livesearch = forms.CharField(
-        required=False,
-        label='Search',
-        widget=Livesearch(
-            query_key='q',
-            query_url='ipam-api:ipaddress-list',
-            field_to_update='nat_inside',
-            obj_label='address'
-        )
-    )
     primary_for_parent = forms.BooleanField(
         required=False,
         label='Make this the primary IP for the device/VM'
@@ -606,6 +657,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
             'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site',
             'nat_rack', 'nat_inside', 'tenant_group', 'tenant', 'tags',
         ]
+        widgets = {
+            'status': StaticSelect2(),
+            'role': StaticSelect2(),
+            'vrf': APISelect(
+                api_url="/api/ipam/vrfs/"
+            )
+        }
 
     def __init__(self, *args, **kwargs):
 
@@ -685,6 +743,13 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         fields = [
             'address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant',
         ]
+        widgets = {
+            'status': StaticSelect2(),
+            'role': StaticSelect2(),
+            'vrf': APISelect(
+                api_url="/api/ipam/vrfs/"
+            )
+        }
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -822,7 +887,10 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     vrf = forms.ModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
-        label='VRF'
+        label='VRF',
+        widget=APISelect(
+            api_url="/api/ipam/vrfs/"
+        )
     )
     mask_length = forms.IntegerField(
         min_value=1,
@@ -831,15 +899,20 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
     )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/tenancy/tenants/"
+        )
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
-        required=False
+        required=False,
+        widget=StaticSelect2()
     )
     role = forms.ChoiceField(
         choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
-        required=False
+        required=False,
+        widget=StaticSelect2()
     )
     description = forms.CharField(
         max_length=100, required=False
@@ -856,7 +929,10 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
         queryset=VRF.objects.all(),
         required=False,
         label='VRF',
-        empty_label='Global'
+        empty_label='Global',
+        widget=APISelect(
+            api_url="/api/ipam/vrfs/"
+        )
     )
     address = forms.CharField(
         label='IP Address'
@@ -881,40 +957,45 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
     family = forms.ChoiceField(
         required=False,
         choices=IP_FAMILY_CHOICES,
-        label='Address family'
+        label='Address family',
+        widget=StaticSelect2()
     )
     mask_length = forms.ChoiceField(
         required=False,
         choices=IPADDRESS_MASK_LENGTH_CHOICES,
-        label='Mask length'
+        label='Mask length',
+        widget=StaticSelect2()
     )
     vrf = FilterChoiceField(
-        queryset=VRF.objects.annotate(
-            filter_count=Count('ip_addresses')
-        ),
+        queryset=VRF.objects.all(),
         to_field_name='rd',
         label='VRF',
-        null_label='-- Global --'
+        null_label='-- Global --',
+        widget=APISelectMultiple(
+            api_url="/api/ipam/vrfs/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(
-            filter_count=Count('ip_addresses')
-        ),
+        queryset=Tenant.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
     )
-    status = AnnotatedMultipleChoiceField(
+    status = forms.MultipleChoiceField(
         choices=IPADDRESS_STATUS_CHOICES,
-        annotate=IPAddress.objects.all(),
-        annotate_field='status',
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
-    role = AnnotatedMultipleChoiceField(
+    role = forms.MultipleChoiceField(
         choices=IPADDRESS_ROLE_CHOICES,
-        annotate=IPAddress.objects.all(),
-        annotate_field='role',
-        include_null=True,
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
 
 
@@ -930,6 +1011,11 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
         fields = [
             'site', 'name', 'slug',
         ]
+        widgets = {
+            'site': APISelect(
+                api_url="/api/dcim/sites/"
+            )
+        }
 
 
 class VLANGroupCSVForm(forms.ModelForm):
@@ -954,11 +1040,14 @@ class VLANGroupCSVForm(forms.ModelForm):
 
 class VLANGroupFilterForm(BootstrapMixin, forms.Form):
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(
-            filter_count=Count('vlan_groups')
-        ),
+        queryset=Site.objects.all(),
         to_field_name='slug',
-        null_label='-- Global --'
+        null_label='-- Global --',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+            null_option=True,
+        )
     )
 
 
@@ -970,9 +1059,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url="/api/dcim/sites/",
+            filter_for={
+                'group': 'site_id'
+            },
             attrs={
-                'filter-for': 'group',
                 'nullable': 'true',
             }
         )
@@ -985,7 +1077,7 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         required=False,
         label='Group',
         widget=APISelect(
-            api_url='/api/ipam/vlan-groups/?site_id={{site}}',
+            api_url='/api/ipam/vlan-groups/',
         )
     )
     tags = TagField(required=False)
@@ -1003,6 +1095,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             'status': "Operational status of this VLAN",
             'role': "The primary function of this VLAN",
         }
+        widgets = {
+            'status': StaticSelect2(),
+            'role': APISelect(
+                api_url="/api/ipam/roles/"
+            )
+        }
 
 
 class VLANCSVForm(forms.ModelForm):
@@ -1078,23 +1176,36 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
     )
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/dcim/sites/"
+        )
     )
     group = forms.ModelChoiceField(
         queryset=VLANGroup.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/ipam/vlan-groups/"
+        )
     )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/tenancy/tenants/"
+        )
     )
     status = forms.ChoiceField(
         choices=add_blank_choice(VLAN_STATUS_CHOICES),
-        required=False
+        required=False,
+        widget=StaticSelect2()
     )
     role = forms.ModelChoiceField(
         queryset=Role.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/ipam/roles/"
+        )
     )
     description = forms.CharField(
         max_length=100,
@@ -1114,38 +1225,48 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
         label='Search'
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(
-            filter_count=Count('vlans')
-        ),
+        queryset=Site.objects.all(),
         to_field_name='slug',
-        null_label='-- Global --'
+        null_label='-- Global --',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     group_id = FilterChoiceField(
-        queryset=VLANGroup.objects.annotate(
-            filter_count=Count('vlans')
-        ),
+        queryset=VLANGroup.objects.all(),
         label='VLAN group',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/ipam/vlan-groups/",
+            null_option=True,
+        )
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(
-            filter_count=Count('vlans')
-        ),
+        queryset=Tenant.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
     )
-    status = AnnotatedMultipleChoiceField(
+    status = forms.MultipleChoiceField(
         choices=VLAN_STATUS_CHOICES,
-        annotate=VLAN.objects.all(),
-        annotate_field='status',
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
     role = FilterChoiceField(
-        queryset=Role.objects.annotate(
-            filter_count=Count('vlans')
-        ),
+        queryset=Role.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/ipam/roles/",
+            value_field="slug",
+            null_option=True,
+        )
     )
 
 
@@ -1167,6 +1288,10 @@ class ServiceForm(BootstrapMixin, CustomFieldForm):
             'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
                            "reachable via all IPs assigned to the device.",
         }
+        widgets = {
+            'protocol': StaticSelect2(),
+            'ipaddresses': StaticSelect2Multiple(),
+        }
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -1193,10 +1318,11 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
     )
     protocol = forms.ChoiceField(
         choices=add_blank_choice(IP_PROTOCOL_CHOICES),
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
     port = forms.IntegerField(
-        required=False
+        required=False,
     )
 
 
@@ -1207,7 +1333,8 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
     )
     protocol = forms.ChoiceField(
         choices=add_blank_choice(IP_PROTOCOL_CHOICES),
-        required=False
+        required=False,
+        widget=StaticSelect2()
     )
     port = forms.IntegerField(
         validators=[
@@ -1223,5 +1350,5 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
 
     class Meta:
         nullable_fields = [
-            'site', 'group', 'tenant', 'role', 'description',
+            'site', 'tenant', 'role', 'description',
         ]

+ 111 - 0
netbox/project-static/css/base.css

@@ -120,6 +120,117 @@ input[name="pk"] {
     margin-top: 0;
 }
 
+/* Color Selections */
+.color-selection-aa1409 {
+    background-color: #aa1409;
+    color: #ffffff;
+}
+.color-selection-f44336 {
+    background-color: #f44336;
+    color: #ffffff;
+}
+.color-selection-e91e63 {
+    background-color: #e91e63;
+    color: #ffffff;
+}
+.color-selection-ffe4e1 {
+    background-color: #ffe4e1;
+    color: #000000;
+}
+.color-selection-ff66ff {
+    background-color: #ff66ff;
+    color: #ffffff;
+}
+.color-selection-9c27b0 {
+    background-color: #9c27b0;
+    color: #ffffff;
+}
+.color-selection-673ab7 {
+    background-color: #673ab7;
+    color: #ffffff;
+}
+.color-selection-3f51b5 {
+    background-color: #3f51b5;
+    color: #ffffff;
+}
+.color-selection-2196f3 {
+    background-color: #2196f3;
+    color: #ffffff;
+}
+.color-selection-03a9f4 {
+    background-color: #03a9f4;
+    color: #ffffff;
+}
+.color-selection-00bcd4 {
+    background-color: #00bcd4;
+    color: #ffffff;
+}
+.color-selection-009688 {
+    background-color: #009688;
+    color: #ffffff;
+}
+.color-selection-00ffff {
+    background-color: #00ffff;
+    color: #ffffff;
+}
+.color-selection-2f6a31 {
+    background-color: #2f6a31;
+    color: #ffffff;
+}
+.color-selection-4caf50 {
+    background-color: #4caf50;
+    color: #ffffff;
+}
+.color-selection-8bc34a {
+    background-color: #8bc34a;
+    color: #ffffff;
+}
+.color-selection-cddc39 {
+    background-color: #cddc39;
+    color: #000000;
+}
+.color-selection-ffeb3b {
+    background-color: #ffeb3b;
+    color: #000000;
+}
+.color-selection-ffc107 {
+    background-color: #ffc107;
+    color: #000000;
+}
+.color-selection-ff9800 {
+    background-color: #ff9800;
+    color: #ffffff;
+}
+.color-selection-ff5722 {
+    background-color: #ff5722;
+    color: #ffffff;
+}
+.color-selection-795548 {
+    background-color: #795548;
+    color: #ffffff;
+}
+.color-selection-c0c0c0 {
+    background-color: #c0c0c0;
+    color: #000000;
+}
+.color-selection-9e9e9e {
+    background-color: #9e9e9e;
+    color: #ffffff;
+}
+.color-selection-607d8b {
+    background-color: #607d8b;
+    color: #ffffff;
+}
+.color-selection-111111 {
+    background-color: #111111;
+    color: #ffffff;
+}
+.color-selection-ffffff {
+    background-color: #ffffff;
+    color: #000000;
+}
+
+
 /* Tables */
 th.pk, td.pk {
     padding-bottom: 6px;

+ 194 - 108
netbox/project-static/js/forms.js

@@ -67,135 +67,221 @@ $(document).ready(function() {
         form.submit();
     });
 
-    // API select widget
-    $('select[filter-for]').change(function() {
+    // Parse URLs which may contain variable refrences to other field values
+    function parseURL(url) {
+        var filter_regex = /\{\{([a-z_]+)\}\}/g;
+        var match;
+        var rendered_url = url;
+        var filter_field;
+        while (match = filter_regex.exec(url)) {
+            filter_field = $('#id_' + match[1]);
+            var custom_attr = $('option:selected', filter_field).attr('api-value');
+            if (custom_attr) {
+                rendered_url = rendered_url.replace(match[0], custom_attr);
+            } else if (filter_field.val()) {
+                rendered_url = rendered_url.replace(match[0], filter_field.val());
+            } else if (filter_field.attr('nullable') == 'true') {
+                rendered_url = rendered_url.replace(match[0], 'null');
+            }
+        }
+        return rendered_url
+    }
+
+    // Assign color picker selection classes
+    function colorPickerClassCopy(data, container) {
+        if (data.element) {
+            $(container).addClass($(data.element).attr("class"));
+        }
+        return data.text;
+    }
 
-        // Resolve child field by ID specified in parent
-        var child_names = $(this).attr('filter-for');
-        var parent = this;
+    // Color Picker
+    $('.netbox-select2-color-picker').select2({
+        allowClear: true,
+        placeholder: "---------",
+        templateResult: colorPickerClassCopy,
+        templateSelection: colorPickerClassCopy,
+    })
 
-        // allow more than one child
-        $.each(child_names.split(" "), function(_, child_name){
+    // Static choice selection
+    $('.netbox-select2-static').select2({
+        allowClear: true,
+        placeholder: "---------",
+    })
 
-            var child_field = $('#id_' + child_name);
-            var child_selected = child_field.val();
+    // API backed selection
+    // Includes live search and chained fields
+    // The `multiple` setting may be controled via a data-* attribute
+    $('.netbox-select2-api').select2({
+        allowClear: true,
+        placeholder: "---------",
 
-            // Wipe out any existing options within the child field and create a default option
-            child_field.empty();
-            if (!child_field.attr('multiple')) {
-                child_field.append($("<option></option>").attr("value", "").text("---------"));
-            }
+        ajax: {
+            delay: 500,
 
-            if ($(parent).val() || $(parent).attr('nullable') == 'true') {
-                var api_url = child_field.attr('api-url') + '&limit=0&brief=1';
-                var disabled_indicator = child_field.attr('disabled-indicator');
-                var initial_value = child_field.attr('initial');
-                var display_field = child_field.attr('display-field') || 'name';
-
-                // Determine the filter fields needed to make an API call
-                var filter_regex = /\{\{([a-z_]+)\}\}/g;
-                var match;
-                var rendered_url = api_url;
-                var filter_field;
-                while (match = filter_regex.exec(api_url)) {
-                    filter_field = $('#id_' + match[1]);
-                    var custom_attr = $('option:selected', filter_field).attr('api-value');
-                    if (custom_attr) {
-                        rendered_url = rendered_url.replace(match[0], custom_attr);
-                    } else if (filter_field.val()) {
-                        rendered_url = rendered_url.replace(match[0], filter_field.val());
-                    } else if (filter_field.attr('nullable') == 'true') {
-                        rendered_url = rendered_url.replace(match[0], 'null');
-                    }
+            url: function(params) {
+                var element = this[0];
+                var url = parseURL(element.getAttribute("data-url"));
+
+                if (url.includes("{{")) {
+                    // URL is not fully rendered yet, abort the request
+                    return false;
                 }
+                return url;
+            },
+
+            data: function(params) {
+                var element = this[0];
+                // Paging
+                var offset = params.page * 50 || 0;
+                // Base query params
+                var parameters = {
+                    q: params.term,
+                    brief: 1,
+                    limit: 50,
+                    offset: offset,
+                };
 
-                // Account for any conditional URL append strings
-                $.each(child_field[0].attributes, function(index, attr){
-                    if (attr.name.includes("data-url-conditional-append-")){
-                        var conditional = attr.name.split("data-url-conditional-append-")[1].split("__");
+                // filter-for fields from a chain
+                var attr_name = "data-filter-for-" + $(element).attr("name");
+                var form = $(element).closest('form');
+                var filter_for_elements = form.find("select[" + attr_name + "]");
+
+                filter_for_elements.each(function(index, filter_for_element) {
+                    var param_name = $(filter_for_element).attr(attr_name);
+                    var value = $(filter_for_element).val();
+
+                    if (param_name && value) {
+                        parameters[param_name] = value;
+                    }
+                });
+
+                // Conditional query params
+                $.each(element.attributes, function(index, attr){
+                    if (attr.name.includes("data-conditional-query-param-")){
+                        var conditional = attr.name.split("data-conditional-query-param-")[1].split("__");
                         var field = $("#id_" + conditional[0]);
                         var field_value = conditional[1];
+                        
                         if ($('option:selected', field).attr('api-value') === field_value){
-                            rendered_url = rendered_url + attr.value;
+                            var _val = attr.value.split("=");
+                            parameters[_val[0]] = _val[1];
                         }
                     }
                 })
 
-                // If all URL variables have been replaced, make the API call
-                if (rendered_url.search('{{') < 0) {
-                    console.log(child_name + ": Fetching " + rendered_url);
-                    $.ajax({
-                        url: rendered_url,
-                        dataType: 'json',
-                        success: function(response, status) {
-                            $.each(response.results, function(index, choice) {
-                                var option = $("<option></option>").attr("value", choice.id).text(choice[display_field]);
-                                if (disabled_indicator && choice[disabled_indicator] && choice.id != initial_value) {
-                                    option.attr("disabled", "disabled");
-                                } else if (choice.id == child_selected) {
-                                    option.attr("selected", "selected");
-                                }
-                                child_field.append(option);
-                            });
-                        }
+                // Additional query params
+                $.each(element.attributes, function(index, attr){
+                    if (attr.name.includes("data-additional-query-param-")){
+                        var param_name = attr.name.split("data-additional-query-param-")[1]
+                        parameters[param_name] = attr.value;
+                    }
+                })
+
+                // This will handle params with multiple values (i.e. for list filter forms)
+                return $.param(parameters, true);
+            },
+
+            processResults: function (data) {
+                var element = this.$element[0];
+                var results = $.map(data.results, function (obj) {
+                    obj.text = obj[element.getAttribute('display-field')] || obj.name;
+                    obj.id = obj[element.getAttribute('value-field')] || obj.id;
+
+                    if(element.getAttribute('disabled-indicator') && obj[element.getAttribute('disabled-indicator')]) {
+                        // The disabled-indicator equated to true, so we disable this option
+                        obj.disabled = true;
+                    }
+                    return obj;
+                });
+
+                // Handle the null option
+                if (element.getAttribute('data-null-option')) {
+                    var null_option = $(element).children()[0]
+                    results.unshift({
+                        id: null_option.value,
+                        text: null_option.text
                     });
                 }
 
+                // Check if there are more results to page
+                var page = data.next !== null;
+                return {
+                    results: results,
+                    pagination: {
+                        more: page
+                    }
+                };
             }
-
-            // Trigger change event in case the child field is the parent of another field
-            child_field.change();
-        });
-
+        }
     });
 
-    // Auto-complete tags
-    function split_tags(val) {
-      return val.split(/,\s*/);
+    // API backed tags
+    var tags = $('#id_tags');
+    if (tags.length > 0 && tags.val().length > 0){
+        tags = $('#id_tags').val().split(/,\s*/);
+    } else {
+        tags = [];
     }
-    $("#id_tags")
-      .on("keydown", function(event) {
-        if (event.keyCode === $.ui.keyCode.TAB &&
-            $(this).autocomplete("instance").menu.active) {
-          event.preventDefault();
+    tag_objs = $.map(tags, function (tag) {
+        return {
+            id: tag,
+            text: tag,
+            selected: true
         }
-      })
-      .autocomplete({
-        source: function(request, response) {
-            $.ajax({
-                type: 'GET',
-                url: netbox_api_path + 'extras/tags/',
-                data: 'q=' + split_tags(request.term).pop(),
-                success: function(data) {
-                    var choices = [];
-                    $.each(data.results, function (index, choice) {
-                        choices.push(choice.name);
-                    });
-                    response(choices);
-                }
-            });
-        },
-        search: function() {
-          // Need 3 or more characters to begin searching
-          var term = split_tags(this.value).pop();
-          if (term.length < 3) {
-            return false;
-          }
-        },
-        focus: function() {
-          // prevent value inserted on focus
-          return false;
-        },
-        select: function(event, ui) {
-          var terms = split_tags(this.value);
-          // remove the current input
-          terms.pop();
-          // add the selected item
-          terms.push(ui.item.value);
-          // add placeholder to get the comma-and-space at the end
-          terms.push("");
-          this.value = terms.join(", ");
-          return false;
+    });
+    // Replace the django issued text input with a select element
+    $('#id_tags').replaceWith('<select name="tags" id="id_tags" class="form-control"></select>');
+    $('#id_tags').select2({
+        tags: true,
+        data: tag_objs,
+        multiple: true,
+        allowClear: true,
+        placeholder: "Tags",
+
+        ajax: {
+            delay: 250,
+            url: "/api/extras/tags/",
+
+            data: function(params) {
+                // paging
+                var offset = params.page * 50 || 0;
+                var parameters = {
+                    q: params.term,
+                    brief: 1,
+                    limit: 50,
+                    offset: offset,
+                };
+                return parameters;
+            },
+
+            processResults: function (data) {
+                var results = $.map(data.results, function (obj) {
+                    return {
+                        id: obj.name,
+                        text: obj.name
+                    }
+                });
+
+                // Check if there are more results to page
+                var page = data.next !== null;
+                return {
+                    results: results,
+                    pagination: {
+                        more: page
+                    }
+                };
+            }
+        }
+    });
+    $('#id_tags').closest('form').submit(function(event){
+        // django-taggit can only accept a single comma seperated string value
+        var value = $('#id_tags').val();
+        if (value.length > 0){
+            var final_tags = value.join(', ');
+            $('#id_tags').val(null).trigger('change');
+            var option = new Option(final_tags, final_tags, true, true);
+            $('#id_tags').append(option).trigger('change');
         }
-      });
+    });
 });

+ 21 - 0
netbox/project-static/select2-4.0.5/LICENSE.md

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 123 - 0
netbox/project-static/select2-4.0.5/README.md

@@ -0,0 +1,123 @@
+Select2
+=======
+[![Build Status][travis-ci-image]][travis-ci-status]
+
+Select2 is a jQuery-based replacement for select boxes. It supports searching,
+remote data sets, and pagination of results.
+
+To get started, checkout examples and documentation at
+https://select2.org/
+
+Use cases
+---------
+* Enhancing native selects with search.
+* Enhancing native selects with a better multi-select interface.
+* Loading data from JavaScript: easily load items via AJAX and have them
+  searchable.
+* Nesting optgroups: native selects only support one level of nesting. Select2
+  does not have this restriction.
+* Tagging: ability to add new items on the fly.
+* Working with large, remote datasets: ability to partially load a dataset based
+  on the search term.
+* Paging of large datasets: easy support for loading more pages when the results
+  are scrolled to the end.
+* Templating: support for custom rendering of results and selections.
+
+Browser compatibility
+---------------------
+* IE 8+
+* Chrome 8+
+* Firefox 10+
+* Safari 3+
+* Opera 10.6+
+
+Select2 is automatically tested on the following browsers.
+
+[![Sauce Labs Test Status][saucelabs-matrix]][saucelabs-status]
+
+Usage
+-----
+You can source Select2 directly from a CDN like [JSDliver][jsdelivr] or
+[CDNJS][cdnjs], [download it from this GitHub repo][releases], or use one of
+the integrations below.
+
+Integrations
+------------
+Third party developers have created plugins for platforms which allow Select2 to be integrated more natively and quickly. For many platforms, additional plugins are not required because Select2 acts as a standard `<select>` box.
+
+Plugins
+
+* [Django]
+  - [django-autocomplete-light]
+  - [django-easy-select2]
+  - [django-select2]
+* [Meteor] - [meteor-select2]
+* [Ruby on Rails][ruby-on-rails] - [select2-rails]
+* [Wicket] - [wicketstuff-select2]
+* [Yii 2][yii2] - [yii2-widget-select2]
+
+Themes
+
+- [Bootstrap 3][bootstrap3] - [select2-bootstrap-theme]
+- [Flat UI][flat-ui] - [select2-flat-theme]
+- [Metro UI][metro-ui] - [select2-metro]
+
+Missing an integration? Modify this `README` and make a pull request back here to Select2 on GitHub.
+
+Internationalization (i18n)
+---------------------------
+Select2 supports multiple languages by simply including the right language JS
+file (`dist/js/i18n/it.js`, `dist/js/i18n/nl.js`, etc.) after
+`dist/js/select2.js`.
+
+Missing a language? Just copy `src/js/select2/i18n/en.js`, translate it, and
+make a pull request back to Select2 here on GitHub.
+
+Documentation
+-------------
+The documentation for Select2 is available
+[through GitHub Pages][documentation] and is located within this repository
+in the [`docs` folder][documentation-folder].
+
+Community
+---------
+You can find out about the different ways to get in touch with the Select2
+community at the [Select2 community page][community].
+
+Copyright and license
+---------------------
+The license is available within the repository in the [LICENSE][license] file.
+
+[cdnjs]: http://www.cdnjs.com/libraries/select2
+[community]: https://select2.org/getting-help
+[documentation]: https://select2.org
+[documentation-folder]: https://github.com/select2/select2/tree/master/docs
+[freenode]: https://freenode.net/
+[jsdelivr]: http://www.jsdelivr.com/#!select2
+[license]: LICENSE.md
+[releases]: https://github.com/select2/select2/releases
+[saucelabs-matrix]: https://saucelabs.com/browser-matrix/select2.svg
+[saucelabs-status]: https://saucelabs.com/u/select2
+[travis-ci-image]: https://img.shields.io/travis/select2/select2/master.svg
+[travis-ci-status]: https://travis-ci.org/select2/select2
+
+[bootstrap3]: https://getbootstrap.com/
+[django]: https://www.djangoproject.com/
+[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
+[django-easy-select2]: https://github.com/asyncee/django-easy-select2
+[django-select2]: https://github.com/applegrew/django-select2
+[flat-ui]: http://designmodo.github.io/Flat-UI/
+[meteor]: https://www.meteor.com/
+[meteor-select2]: https://github.com/nate-strauser/meteor-select2
+[metro-ui]: http://metroui.org.ua/
+[select2-metro]: http://metroui.org.ua/select2.html
+[ruby-on-rails]: http://rubyonrails.org/
+[select2-bootstrap-theme]: https://github.com/select2/select2-bootstrap-theme
+[select2-flat-theme]: https://github.com/techhysahil/select2-Flat_Theme
+[select2-rails]: https://github.com/argerim/select2-rails
+[vue.js]: http://vuejs.org/
+[select2-vue]: http://vuejs.org/examples/select2.html
+[wicket]: https://wicket.apache.org/
+[wicketstuff-select2]: https://github.com/wicketstuff/core/tree/master/select2-parent
+[yii2]: http://www.yiiframework.com/
+[yii2-widget-select2]: https://github.com/kartik-v/yii2-widget-select2

+ 484 - 0
netbox/project-static/select2-4.0.5/css/select2.css

@@ -0,0 +1,484 @@
+.select2-container {
+  box-sizing: border-box;
+  display: inline-block;
+  margin: 0;
+  position: relative;
+  vertical-align: middle; }
+  .select2-container .select2-selection--single {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    height: 28px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--single .select2-selection__rendered {
+      display: block;
+      padding-left: 8px;
+      padding-right: 20px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+    .select2-container .select2-selection--single .select2-selection__clear {
+      position: relative; }
+  .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
+    padding-right: 8px;
+    padding-left: 20px; }
+  .select2-container .select2-selection--multiple {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    min-height: 32px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--multiple .select2-selection__rendered {
+      display: inline-block;
+      overflow: hidden;
+      padding-left: 8px;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+  .select2-container .select2-search--inline {
+    float: left; }
+    .select2-container .select2-search--inline .select2-search__field {
+      box-sizing: border-box;
+      border: none;
+      font-size: 100%;
+      margin-top: 5px;
+      padding: 0; }
+      .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
+        -webkit-appearance: none; }
+
+.select2-dropdown {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  box-sizing: border-box;
+  display: block;
+  position: absolute;
+  left: -100000px;
+  width: 100%;
+  z-index: 1051; }
+
+.select2-results {
+  display: block; }
+
+.select2-results__options {
+  list-style: none;
+  margin: 0;
+  padding: 0; }
+
+.select2-results__option {
+  padding: 6px;
+  user-select: none;
+  -webkit-user-select: none; }
+  .select2-results__option[aria-selected] {
+    cursor: pointer; }
+
+.select2-container--open .select2-dropdown {
+  left: 0; }
+
+.select2-container--open .select2-dropdown--above {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--open .select2-dropdown--below {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-search--dropdown {
+  display: block;
+  padding: 4px; }
+  .select2-search--dropdown .select2-search__field {
+    padding: 4px;
+    width: 100%;
+    box-sizing: border-box; }
+    .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
+      -webkit-appearance: none; }
+  .select2-search--dropdown.select2-search--hide {
+    display: none; }
+
+.select2-close-mask {
+  border: 0;
+  margin: 0;
+  padding: 0;
+  display: block;
+  position: fixed;
+  left: 0;
+  top: 0;
+  min-height: 100%;
+  min-width: 100%;
+  height: auto;
+  width: auto;
+  opacity: 0;
+  z-index: 99;
+  background-color: #fff;
+  filter: alpha(opacity=0); }
+
+.select2-hidden-accessible {
+  border: 0 !important;
+  clip: rect(0 0 0 0) !important;
+  height: 1px !important;
+  margin: -1px !important;
+  overflow: hidden !important;
+  padding: 0 !important;
+  position: absolute !important;
+  width: 1px !important; }
+
+.select2-container--default .select2-selection--single {
+  background-color: #fff;
+  border: 1px solid #aaa;
+  border-radius: 4px; }
+  .select2-container--default .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--default .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold; }
+  .select2-container--default .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--default .select2-selection--single .select2-selection__arrow {
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px; }
+    .select2-container--default .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  left: 1px;
+  right: auto; }
+
+.select2-container--default.select2-container--disabled .select2-selection--single {
+  background-color: #eee;
+  cursor: default; }
+  .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
+    display: none; }
+
+.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
+  border-color: transparent transparent #888 transparent;
+  border-width: 0 4px 5px 4px; }
+
+.select2-container--default .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text; }
+  .select2-container--default .select2-selection--multiple .select2-selection__rendered {
+    box-sizing: border-box;
+    list-style: none;
+    margin: 0;
+    padding: 0 5px;
+    width: 100%; }
+    .select2-container--default .select2-selection--multiple .select2-selection__rendered li {
+      list-style: none; }
+  .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
+    color: #999;
+    margin-top: 5px;
+    float: left; }
+  .select2-container--default .select2-selection--multiple .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-top: 5px;
+    margin-right: 10px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+    color: #999;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #333; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+  float: right; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+  border: solid black 1px;
+  outline: 0; }
+
+.select2-container--default.select2-container--disabled .select2-selection--multiple {
+  background-color: #eee;
+  cursor: default; }
+
+.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
+  display: none; }
+
+.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--default .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa; }
+
+.select2-container--default .select2-search--inline .select2-search__field {
+  background: transparent;
+  border: none;
+  outline: 0;
+  box-shadow: none;
+  -webkit-appearance: textfield; }
+
+.select2-container--default .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--default .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--default .select2-results__option[aria-disabled=true] {
+  color: #999; }
+
+.select2-container--default .select2-results__option[aria-selected=true] {
+  background-color: #ddd; }
+
+.select2-container--default .select2-results__option .select2-results__option {
+  padding-left: 1em; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__group {
+    padding-left: 0; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -1em;
+    padding-left: 2em; }
+    .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+      margin-left: -2em;
+      padding-left: 3em; }
+      .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+        margin-left: -3em;
+        padding-left: 4em; }
+        .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+          margin-left: -4em;
+          padding-left: 5em; }
+          .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+            margin-left: -5em;
+            padding-left: 6em; }
+
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+  background-color: #5897fb;
+  color: white; }
+
+.select2-container--default .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic .select2-selection--single {
+  background-color: #f7f7f7;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  outline: 0;
+  background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+  .select2-container--classic .select2-selection--single:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--classic .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-right: 10px; }
+  .select2-container--classic .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--classic .select2-selection--single .select2-selection__arrow {
+    background-color: #ddd;
+    border: none;
+    border-left: 1px solid #aaa;
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px;
+    background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
+    background-repeat: repeat-x;
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
+    .select2-container--classic .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  border: none;
+  border-right: 1px solid #aaa;
+  border-radius: 0;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+  left: 1px;
+  right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--single {
+  border: 1px solid #5897fb; }
+  .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
+    background: transparent;
+    border: none; }
+    .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
+      border-color: transparent transparent #888 transparent;
+      border-width: 0 4px 5px 4px; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
+
+.select2-container--classic .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text;
+  outline: 0; }
+  .select2-container--classic .select2-selection--multiple:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__rendered {
+    list-style: none;
+    margin: 0;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__clear {
+    display: none; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
+    color: #888;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #555; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  float: right; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--multiple {
+  border: 1px solid #5897fb; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--classic .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa;
+  outline: 0; }
+
+.select2-container--classic .select2-search--inline .select2-search__field {
+  outline: 0;
+  box-shadow: none; }
+
+.select2-container--classic .select2-dropdown {
+  background-color: white;
+  border: 1px solid transparent; }
+
+.select2-container--classic .select2-dropdown--above {
+  border-bottom: none; }
+
+.select2-container--classic .select2-dropdown--below {
+  border-top: none; }
+
+.select2-container--classic .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--classic .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--classic .select2-results__option[aria-disabled=true] {
+  color: grey; }
+
+.select2-container--classic .select2-results__option--highlighted[aria-selected] {
+  background-color: #3875d7;
+  color: white; }
+
+.select2-container--classic .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic.select2-container--open .select2-dropdown {
+  border-color: #5897fb; }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
netbox/project-static/select2-4.0.5/css/select2.min.css


+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/af.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/af",[],function(){return{errorLoading:function(){return"Die resultate kon nie gelaai word nie."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Verwyders asseblief "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Voer asseblief "+t+" of meer karakters";return n},loadingMore:function(){return"Meer resultate word gelaai…"},maximumSelected:function(e){var t="Kies asseblief net "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"Geen resultate gevind"},searching:function(){return"Besig…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ar.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(e){var t=e.input.length-e.maximum;return"الرجاء حذف "+t+" عناصر"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"الرجاء إضافة "+t+" عناصر"},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(e){return"تستطيع إختيار "+e.maximum+" بنود فقط"},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/az.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/az",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return t+" simvol silin"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(e){return"Sadəcə "+e.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/bg.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bg",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Моля въведете с "+t+" по-малко символ";return t>1&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/bs.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bs",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspijelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ca.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/cs.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadejte o jeden znak méně.":n<=4?"Prosím, zadejte o "+e(n,!0)+" znaky méně.":"Prosím, zadejte o "+n+" znaků méně."},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadejte ještě jeden znak.":n<=4?"Prosím, zadejte ještě další "+e(n,!0)+" znaky.":"Prosím, zadejte ještě dalších "+n+" znaků."},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku.":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky.":"Můžete zvolit maximálně "+n+" položek."},noResults:function(){return"Nenalezeny žádné položky."},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/da.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Angiv venligst "+t+" tegn mindre"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Angiv venligst "+t+" tegn mere"},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/de.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{errorLoading:function(){return"Die Ergebnisse konnten nicht geladen werden."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/dsb.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/dsb",[],function(){var e=["znamuško","znamušce","znamuška","znamuškow"],t=["zapisk","zapiska","zapiski","zapiskow"],n=function(t,n){if(t===1)return n[0];if(t===2)return n[1];if(t>2&&t<=4)return n[2];if(t>=5)return n[3]};return{errorLoading:function(){return"Wuslědki njejsu se dali zacytaś."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Pšosym lašuj "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Pšosym zapódaj nanejmjenjej "+r+" "+n(r,e)},loadingMore:function(){return"Dalšne wuslědki se zacytaju…"},maximumSelected:function(e){return"Móžoš jano "+e.maximum+" "+n(e.maximum,t)+"wubraś."},noResults:function(){return"Žedne wuslědki namakane"},searching:function(){return"Pyta se…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/el.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/en.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/es.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/et.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/eu.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/fa.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها می‌توانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/fi.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{errorLoading:function(){return"Tuloksia ei saatu ladattua."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){return"Haetaan…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/fr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Supprimez "+t+" caractère"+(t>1)?"s":""},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Saisissez au moins "+t+" caractère"+(t>1)?"s":""},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){return"Vous pouvez seulement sélectionner "+e.maximum+" élément"+(e.maximum>1)?"s":""},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/gl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{errorLoading:function(){return"Non foi posíbel cargar os resultados."},inputTooLong:function(e){var t=e.input.length-e.maximum;return t===1?"Elimine un carácter":"Elimine "+t+" caracteres"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t===1?"Engada un carácter":"Engada "+t+" caracteres"},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){return e.maximum===1?"Só pode seleccionar un elemento":"Só pode seleccionar "+e.maximum+" elementos"},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/he.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="נא למחוק ";return t===1?n+="תו אחד":n+=t+" תווים",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="נא להכניס ";return t===1?n+="תו אחד":n+=t+" תווים",n+=" או יותר",n},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(e){var t="באפשרותך לבחור עד ";return e.maximum===1?t+="פריט אחד":t+=e.maximum+" פריטים",t},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/hi.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/hr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/hsb.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hsb",[],function(){var e=["znamješko","znamješce","znamješka","znamješkow"],t=["zapisk","zapiskaj","zapiski","zapiskow"],n=function(t,n){if(t===1)return n[0];if(t===2)return n[1];if(t>2&&t<=4)return n[2];if(t>=5)return n[3]};return{errorLoading:function(){return"Wuslědki njedachu so začitać."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Prošu zhašej "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Prošu zapodaj znajmjeńša "+r+" "+n(r,e)},loadingMore:function(){return"Dalše wuslědki so začitaja…"},maximumSelected:function(e){return"Móžeš jenož "+e.maximum+" "+n(e.maximum,t)+"wubrać"},noResults:function(){return"Žane wuslědki namakane"},searching:function(){return"Pyta so…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/hu.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{errorLoading:function(){return"Az eredmények betöltése nem sikerült."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/hy.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hy",[],function(){return{errorLoading:function(){return"Արդյունքները հնարավոր չէ բեռնել։"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Խնդրում ենք հեռացնել "+t+" նշան";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Խնդրում ենք մուտքագրել "+t+" կամ ավել նշաններ";return n},loadingMore:function(){return"Բեռնվում են նոր արդյունքներ․․․"},maximumSelected:function(e){var t="Դուք կարող եք ընտրել առավելագույնը "+e.maximum+" կետ";return t},noResults:function(){return"Արդյունքներ չեն գտնվել"},searching:function(){return"Որոնում․․․"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/id.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/is.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/it.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ja.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" 文字を削除してください";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="少なくとも "+t+" 文字を入力してください";return n},loadingMore:function(){return"読み込み中…"},maximumSelected:function(e){var t=e.maximum+" 件しか選択できません";return t},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/km.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="សូមលុបចេញ  "+t+" អក្សរ";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="សូមបញ្ចូល"+t+" អក្សរ រឺ ច្រើនជាងនេះ";return n},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(e){var t="អ្នកអាចជ្រើសរើសបានតែ "+e.maximum+" ជម្រើសប៉ុណ្ណោះ";return t},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ko.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/lt.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%10===1&&(e%100<11||e%100>19)?t:e%10>=2&&e%10<=9&&(e%100<11||e%100>19)?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"į","ius","ių"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"į","ius","ių"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ą","us","ų"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/lv.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par  "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/mk.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ms.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Sila hapuskan "+t+" aksara"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Sila masukkan "+t+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(e){return"Anda hanya boleh memilih "+e.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/nb.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn "+t+" tegn til";return n+" tegn til"},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/nl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t=e.maximum==1?"kan":"kunnen",n="Er "+t+" maar "+e.maximum+" item";return e.maximum!=1&&(n+="s"),n+=" worden geselecteerd",n},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/pl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maximum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ps.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ps",[],function(){return{errorLoading:function(){return"پايلي نه سي ترلاسه کېدای"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="د مهربانۍ لمخي "+t+" توری ړنګ کړئ";return t!=1&&(n=n.replace("توری","توري")),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لږ تر لږه "+t+" يا ډېر توري وليکئ";return n},loadingMore:function(){return"نوري پايلي ترلاسه کيږي..."},maximumSelected:function(e){var t="تاسو يوازي "+e.maximum+" قلم په نښه کولای سی";return e.maximum!=1&&(t=t.replace("قلم","قلمونه")),t},noResults:function(){return"پايلي و نه موندل سوې"},searching:function(){return"لټول کيږي..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/pt-BR.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/pt.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"caractere",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ro.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return t!==1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vă rugăm să introduceți "+t+" sau mai multe caractere";return n},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",e.maximum!==1&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/ru.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ru",[],function(){function e(e,t,n,r){return e%10<5&&e%10>0&&e%100<5||e%100>20?e%10>1?n:t:r}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Пожалуйста, введите на "+n+" символ";return r+=e(n,"","a","ов"),r+=" меньше",r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Пожалуйста, введите еще хотя бы "+n+" символ";return r+=e(n,"","a","ов"),r},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(t){var n="Вы можете выбрать не более "+t.maximum+" элемент";return n+=e(t.maximum,"","a","ов"),n},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/sk.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{errorLoading:function(){return"Výsledky sa nepodarilo načítať."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadajte o jeden znak menej":n>=2&&n<=4?"Prosím, zadajte o "+e[n](!0)+" znaky menej":"Prosím, zadajte o "+n+" znakov menej"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadajte ešte jeden znak":n<=4?"Prosím, zadajte ešte ďalšie "+e[n](!0)+" znaky":"Prosím, zadajte ešte ďalších "+n+" znakov"},loadingMore:function(){return"Načítanie ďalších výsledkov…"},maximumSelected:function(t){return t.maximum==1?"Môžete zvoliť len jednu položku":t.maximum>=2&&t.maximum<=4?"Môžete zvoliť najviac "+e[t.maximum](!1)+" položky":"Môžete zvoliť najviac "+t.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/sl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sl",[],function(){return{errorLoading:function(){return"Zadetkov iskanja ni bilo mogoče naložiti."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Prosim zbrišite "+t+" znak";return t==2?n+="a":t!=1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Prosim vpišite še "+t+" znak";return t==2?n+="a":t!=1&&(n+="e"),n},loadingMore:function(){return"Nalagam več zadetkov…"},maximumSelected:function(e){var t="Označite lahko največ "+e.maximum+" predmet";return e.maximum==2?t+="a":e.maximum!=1&&(t+="e"),t},noResults:function(){return"Ni zadetkov."},searching:function(){return"Iščem…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/sr-Cyrl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr-Cyrl",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Обришите "+n+" симбол";return r+=e(n,"","а","а"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Укуцајте бар још "+n+" симбол";return r+=e(n,"","а","а"),r},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(t){var n="Можете изабрати само "+t.maximum+" ставк";return n+=e(t.maximum,"у","е","и"),n},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/sr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/sv.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vänligen sudda ut "+t+" tecken";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vänligen skriv in "+t+" eller fler tecken";return n},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(e){var t="Du kan max välja "+e.maximum+" element";return t},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/th.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/th",[],function(){return{errorLoading:function(){return"ไม่สามารถค้นข้อมูลได้"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="โปรดลบออก "+t+" ตัวอักษร";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="โปรดพิมพ์เพิ่มอีก "+t+" ตัวอักษร";return n},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(e){var t="คุณสามารถเลือกได้ไม่เกิน "+e.maximum+" รายการ";return t},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/tr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/tr",[],function(){return{errorLoading:function(){return"Sonuç yüklenemedi"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" karakter daha girmelisiniz";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="En az "+t+" karakter daha girmelisiniz";return n},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(e){var t="Sadece "+e.maximum+" seçim yapabilirsiniz";return t},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/uk.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/uk",[],function(){function e(e,t,n,r){return e%100>10&&e%100<15?r:e%10===1?t:e%10>1&&e%10<5?n:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Будь ласка, видаліть "+n+" "+e(t.maximum,"літеру","літери","літер")},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Будь ласка, введіть "+t+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(t){return"Ви можете вибрати лише "+t.maximum+" "+e(t.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/vi.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/vi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vui lòng nhập ít hơn "+t+" ký tự";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vui lòng nhập nhiều hơn "+t+" ký tự";return n},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(e){var t="Chỉ có thể chọn được "+e.maximum+" lựa chọn";return t},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/zh-CN.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="请删除"+t+"个字符";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="请再输入至少"+t+"个字符";return n},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(e){var t="最多只能选择"+e.maximum+"个项目";return t},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
netbox/project-static/select2-4.0.5/js/i18n/zh-TW.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="請刪掉"+t+"個字元";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="請再輸入"+t+"個字元";return n},loadingMore:function(){return"載入中…"},maximumSelected:function(e){var t="你只能選擇最多"+e.maximum+"項";return t},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"}}}),{define:e.define,require:e.require}})();

+ 6457 - 0
netbox/project-static/select2-4.0.5/js/select2.full.js

@@ -0,0 +1,6457 @@
+/*!
+ * Select2 4.0.5
+ * https://select2.github.io
+ *
+ * Released under the MIT license
+ * https://github.com/select2/select2/blob/master/LICENSE.md
+ */
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(['jquery'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    // Node/CommonJS
+    module.exports = function (root, jQuery) {
+      if (jQuery === undefined) {
+        // require('jQuery') returns a factory that requires window to
+        // build a jQuery instance, we normalize how we use modules
+        // that require this pattern but the window provided is a noop
+        // if it's defined (how jquery works)
+        if (typeof window !== 'undefined') {
+          jQuery = require('jquery');
+        }
+        else {
+          jQuery = require('jquery')(root);
+        }
+      }
+      factory(jQuery);
+      return jQuery;
+    };
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+} (function (jQuery) {
+  // This is needed so we can catch the AMD loader configuration and use it
+  // The inner file should be wrapped (by `banner.start.js`) in a function that
+  // returns the AMD loader references.
+  var S2 =(function () {
+  // Restore the Select2 AMD loader so it can be used
+  // Needed mostly in the language files, where the loader is not inserted
+  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
+    var S2 = jQuery.fn.select2.amd;
+  }
+var S2;(function () { if (!S2 || !S2.requirejs) {
+if (!S2) { S2 = {}; } else { require = S2; }
+/**
+ * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, http://github.com/requirejs/almond/LICENSE
+ */
+//Going sloppy to avoid 'use strict' string cost, but strict practices should
+//be followed.
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+    var main, req, makeMap, handlers,
+        defined = {},
+        waiting = {},
+        config = {},
+        defining = {},
+        hasOwn = Object.prototype.hasOwnProperty,
+        aps = [].slice,
+        jsSuffixRegExp = /\.js$/;
+
+    function hasProp(obj, prop) {
+        return hasOwn.call(obj, prop);
+    }
+
+    /**
+     * Given a relative module name, like ./something, normalize it to
+     * a real name that can be mapped to a path.
+     * @param {String} name the relative name
+     * @param {String} baseName a real name that the name arg is relative
+     * to.
+     * @returns {String} normalized name
+     */
+    function normalize(name, baseName) {
+        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
+            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
+            baseParts = baseName && baseName.split("/"),
+            map = config.map,
+            starMap = (map && map['*']) || {};
+
+        //Adjust any relative paths.
+        if (name) {
+            name = name.split('/');
+            lastIndex = name.length - 1;
+
+            // If wanting node ID compatibility, strip .js from end
+            // of IDs. Have to do this here, and not in nameToUrl
+            // because node allows either .js or non .js to map
+            // to same file.
+            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+            }
+
+            // Starts with a '.' so need the baseName
+            if (name[0].charAt(0) === '.' && baseParts) {
+                //Convert baseName to array, and lop off the last part,
+                //so that . matches that 'directory' and not name of the baseName's
+                //module. For instance, baseName of 'one/two/three', maps to
+                //'one/two/three.js', but we want the directory, 'one/two' for
+                //this normalization.
+                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+                name = normalizedBaseParts.concat(name);
+            }
+
+            //start trimDots
+            for (i = 0; i < name.length; i++) {
+                part = name[i];
+                if (part === '.') {
+                    name.splice(i, 1);
+                    i -= 1;
+                } else if (part === '..') {
+                    // If at the start, or previous value is still ..,
+                    // keep them so that when converted to a path it may
+                    // still work when converted to a path, even though
+                    // as an ID it is less than ideal. In larger point
+                    // releases, may be better to just kick out an error.
+                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
+                        continue;
+                    } else if (i > 0) {
+                        name.splice(i - 1, 2);
+                        i -= 2;
+                    }
+                }
+            }
+            //end trimDots
+
+            name = name.join('/');
+        }
+
+        //Apply map config if available.
+        if ((baseParts || starMap) && map) {
+            nameParts = name.split('/');
+
+            for (i = nameParts.length; i > 0; i -= 1) {
+                nameSegment = nameParts.slice(0, i).join("/");
+
+                if (baseParts) {
+                    //Find the longest baseName segment match in the config.
+                    //So, do joins on the biggest to smallest lengths of baseParts.
+                    for (j = baseParts.length; j > 0; j -= 1) {
+                        mapValue = map[baseParts.slice(0, j).join('/')];
+
+                        //baseName segment has  config, find if it has one for
+                        //this name.
+                        if (mapValue) {
+                            mapValue = mapValue[nameSegment];
+                            if (mapValue) {
+                                //Match, update name to the new value.
+                                foundMap = mapValue;
+                                foundI = i;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (foundMap) {
+                    break;
+                }
+
+                //Check for a star map match, but just hold on to it,
+                //if there is a shorter segment match later in a matching
+                //config, then favor over this star map.
+                if (!foundStarMap && starMap && starMap[nameSegment]) {
+                    foundStarMap = starMap[nameSegment];
+                    starI = i;
+                }
+            }
+
+            if (!foundMap && foundStarMap) {
+                foundMap = foundStarMap;
+                foundI = starI;
+            }
+
+            if (foundMap) {
+                nameParts.splice(0, foundI, foundMap);
+                name = nameParts.join('/');
+            }
+        }
+
+        return name;
+    }
+
+    function makeRequire(relName, forceSync) {
+        return function () {
+            //A version of a require function that passes a moduleName
+            //value for items that may need to
+            //look up paths relative to the moduleName
+            var args = aps.call(arguments, 0);
+
+            //If first arg is not require('string'), and there is only
+            //one arg, it is the array form without a callback. Insert
+            //a null so that the following concat is correct.
+            if (typeof args[0] !== 'string' && args.length === 1) {
+                args.push(null);
+            }
+            return req.apply(undef, args.concat([relName, forceSync]));
+        };
+    }
+
+    function makeNormalize(relName) {
+        return function (name) {
+            return normalize(name, relName);
+        };
+    }
+
+    function makeLoad(depName) {
+        return function (value) {
+            defined[depName] = value;
+        };
+    }
+
+    function callDep(name) {
+        if (hasProp(waiting, name)) {
+            var args = waiting[name];
+            delete waiting[name];
+            defining[name] = true;
+            main.apply(undef, args);
+        }
+
+        if (!hasProp(defined, name) && !hasProp(defining, name)) {
+            throw new Error('No ' + name);
+        }
+        return defined[name];
+    }
+
+    //Turns a plugin!resource to [plugin, resource]
+    //with the plugin being undefined if the name
+    //did not have a plugin prefix.
+    function splitPrefix(name) {
+        var prefix,
+            index = name ? name.indexOf('!') : -1;
+        if (index > -1) {
+            prefix = name.substring(0, index);
+            name = name.substring(index + 1, name.length);
+        }
+        return [prefix, name];
+    }
+
+    //Creates a parts array for a relName where first part is plugin ID,
+    //second part is resource ID. Assumes relName has already been normalized.
+    function makeRelParts(relName) {
+        return relName ? splitPrefix(relName) : [];
+    }
+
+    /**
+     * Makes a name map, normalizing the name, and using a plugin
+     * for normalization if necessary. Grabs a ref to plugin
+     * too, as an optimization.
+     */
+    makeMap = function (name, relParts) {
+        var plugin,
+            parts = splitPrefix(name),
+            prefix = parts[0],
+            relResourceName = relParts[1];
+
+        name = parts[1];
+
+        if (prefix) {
+            prefix = normalize(prefix, relResourceName);
+            plugin = callDep(prefix);
+        }
+
+        //Normalize according
+        if (prefix) {
+            if (plugin && plugin.normalize) {
+                name = plugin.normalize(name, makeNormalize(relResourceName));
+            } else {
+                name = normalize(name, relResourceName);
+            }
+        } else {
+            name = normalize(name, relResourceName);
+            parts = splitPrefix(name);
+            prefix = parts[0];
+            name = parts[1];
+            if (prefix) {
+                plugin = callDep(prefix);
+            }
+        }
+
+        //Using ridiculous property names for space reasons
+        return {
+            f: prefix ? prefix + '!' + name : name, //fullName
+            n: name,
+            pr: prefix,
+            p: plugin
+        };
+    };
+
+    function makeConfig(name) {
+        return function () {
+            return (config && config.config && config.config[name]) || {};
+        };
+    }
+
+    handlers = {
+        require: function (name) {
+            return makeRequire(name);
+        },
+        exports: function (name) {
+            var e = defined[name];
+            if (typeof e !== 'undefined') {
+                return e;
+            } else {
+                return (defined[name] = {});
+            }
+        },
+        module: function (name) {
+            return {
+                id: name,
+                uri: '',
+                exports: defined[name],
+                config: makeConfig(name)
+            };
+        }
+    };
+
+    main = function (name, deps, callback, relName) {
+        var cjsModule, depName, ret, map, i, relParts,
+            args = [],
+            callbackType = typeof callback,
+            usingExports;
+
+        //Use name if no relName
+        relName = relName || name;
+        relParts = makeRelParts(relName);
+
+        //Call the callback to define the module, if necessary.
+        if (callbackType === 'undefined' || callbackType === 'function') {
+            //Pull out the defined dependencies and pass the ordered
+            //values to the callback.
+            //Default to [require, exports, module] if no deps
+            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
+            for (i = 0; i < deps.length; i += 1) {
+                map = makeMap(deps[i], relParts);
+                depName = map.f;
+
+                //Fast path CommonJS standard dependencies.
+                if (depName === "require") {
+                    args[i] = handlers.require(name);
+                } else if (depName === "exports") {
+                    //CommonJS module spec 1.1
+                    args[i] = handlers.exports(name);
+                    usingExports = true;
+                } else if (depName === "module") {
+                    //CommonJS module spec 1.1
+                    cjsModule = args[i] = handlers.module(name);
+                } else if (hasProp(defined, depName) ||
+                           hasProp(waiting, depName) ||
+                           hasProp(defining, depName)) {
+                    args[i] = callDep(depName);
+                } else if (map.p) {
+                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+                    args[i] = defined[depName];
+                } else {
+                    throw new Error(name + ' missing ' + depName);
+                }
+            }
+
+            ret = callback ? callback.apply(defined[name], args) : undefined;
+
+            if (name) {
+                //If setting exports via "module" is in play,
+                //favor that over return value and exports. After that,
+                //favor a non-undefined return value over exports use.
+                if (cjsModule && cjsModule.exports !== undef &&
+                        cjsModule.exports !== defined[name]) {
+                    defined[name] = cjsModule.exports;
+                } else if (ret !== undef || !usingExports) {
+                    //Use the return value from the function.
+                    defined[name] = ret;
+                }
+            }
+        } else if (name) {
+            //May just be an object definition for the module. Only
+            //worry about defining if have a module name.
+            defined[name] = callback;
+        }
+    };
+
+    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
+        if (typeof deps === "string") {
+            if (handlers[deps]) {
+                //callback in this case is really relName
+                return handlers[deps](callback);
+            }
+            //Just return the module wanted. In this scenario, the
+            //deps arg is the module name, and second arg (if passed)
+            //is just the relName.
+            //Normalize module name, if it contains . or ..
+            return callDep(makeMap(deps, makeRelParts(callback)).f);
+        } else if (!deps.splice) {
+            //deps is a config object, not an array.
+            config = deps;
+            if (config.deps) {
+                req(config.deps, config.callback);
+            }
+            if (!callback) {
+                return;
+            }
+
+            if (callback.splice) {
+                //callback is an array, which means it is a dependency list.
+                //Adjust args if there are dependencies
+                deps = callback;
+                callback = relName;
+                relName = null;
+            } else {
+                deps = undef;
+            }
+        }
+
+        //Support require(['a'])
+        callback = callback || function () {};
+
+        //If relName is a function, it is an errback handler,
+        //so remove it.
+        if (typeof relName === 'function') {
+            relName = forceSync;
+            forceSync = alt;
+        }
+
+        //Simulate async callback;
+        if (forceSync) {
+            main(undef, deps, callback, relName);
+        } else {
+            //Using a non-zero value because of concern for what old browsers
+            //do, and latest browsers "upgrade" to 4 if lower value is used:
+            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
+            //If want a value immediately, use require('id') instead -- something
+            //that works in almond on the global level, but not guaranteed and
+            //unlikely to work in other AMD implementations.
+            setTimeout(function () {
+                main(undef, deps, callback, relName);
+            }, 4);
+        }
+
+        return req;
+    };
+
+    /**
+     * Just drops the config on the floor, but returns req in case
+     * the config return value is used.
+     */
+    req.config = function (cfg) {
+        return req(cfg);
+    };
+
+    /**
+     * Expose module registry for debugging and tooling
+     */
+    requirejs._defined = defined;
+
+    define = function (name, deps, callback) {
+        if (typeof name !== 'string') {
+            throw new Error('See almond README: incorrect module build, no module name');
+        }
+
+        //This module may not have dependencies
+        if (!deps.splice) {
+            //deps is not an array, so probably means
+            //an object literal or factory function for
+            //the value. Adjust args.
+            callback = deps;
+            deps = [];
+        }
+
+        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
+            waiting[name] = [name, deps, callback];
+        }
+    };
+
+    define.amd = {
+        jQuery: true
+    };
+}());
+
+S2.requirejs = requirejs;S2.require = require;S2.define = define;
+}
+}());
+S2.define("almond", function(){});
+
+/* global jQuery:false, $:false */
+S2.define('jquery',[],function () {
+  var _$ = jQuery || $;
+
+  if (_$ == null && console && console.error) {
+    console.error(
+      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
+      'found. Make sure that you are including jQuery before Select2 on your ' +
+      'web page.'
+    );
+  }
+
+  return _$;
+});
+
+S2.define('select2/utils',[
+  'jquery'
+], function ($) {
+  var Utils = {};
+
+  Utils.Extend = function (ChildClass, SuperClass) {
+    var __hasProp = {}.hasOwnProperty;
+
+    function BaseConstructor () {
+      this.constructor = ChildClass;
+    }
+
+    for (var key in SuperClass) {
+      if (__hasProp.call(SuperClass, key)) {
+        ChildClass[key] = SuperClass[key];
+      }
+    }
+
+    BaseConstructor.prototype = SuperClass.prototype;
+    ChildClass.prototype = new BaseConstructor();
+    ChildClass.__super__ = SuperClass.prototype;
+
+    return ChildClass;
+  };
+
+  function getMethods (theClass) {
+    var proto = theClass.prototype;
+
+    var methods = [];
+
+    for (var methodName in proto) {
+      var m = proto[methodName];
+
+      if (typeof m !== 'function') {
+        continue;
+      }
+
+      if (methodName === 'constructor') {
+        continue;
+      }
+
+      methods.push(methodName);
+    }
+
+    return methods;
+  }
+
+  Utils.Decorate = function (SuperClass, DecoratorClass) {
+    var decoratedMethods = getMethods(DecoratorClass);
+    var superMethods = getMethods(SuperClass);
+
+    function DecoratedClass () {
+      var unshift = Array.prototype.unshift;
+
+      var argCount = DecoratorClass.prototype.constructor.length;
+
+      var calledConstructor = SuperClass.prototype.constructor;
+
+      if (argCount > 0) {
+        unshift.call(arguments, SuperClass.prototype.constructor);
+
+        calledConstructor = DecoratorClass.prototype.constructor;
+      }
+
+      calledConstructor.apply(this, arguments);
+    }
+
+    DecoratorClass.displayName = SuperClass.displayName;
+
+    function ctr () {
+      this.constructor = DecoratedClass;
+    }
+
+    DecoratedClass.prototype = new ctr();
+
+    for (var m = 0; m < superMethods.length; m++) {
+        var superMethod = superMethods[m];
+
+        DecoratedClass.prototype[superMethod] =
+          SuperClass.prototype[superMethod];
+    }
+
+    var calledMethod = function (methodName) {
+      // Stub out the original method if it's not decorating an actual method
+      var originalMethod = function () {};
+
+      if (methodName in DecoratedClass.prototype) {
+        originalMethod = DecoratedClass.prototype[methodName];
+      }
+
+      var decoratedMethod = DecoratorClass.prototype[methodName];
+
+      return function () {
+        var unshift = Array.prototype.unshift;
+
+        unshift.call(arguments, originalMethod);
+
+        return decoratedMethod.apply(this, arguments);
+      };
+    };
+
+    for (var d = 0; d < decoratedMethods.length; d++) {
+      var decoratedMethod = decoratedMethods[d];
+
+      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
+    }
+
+    return DecoratedClass;
+  };
+
+  var Observable = function () {
+    this.listeners = {};
+  };
+
+  Observable.prototype.on = function (event, callback) {
+    this.listeners = this.listeners || {};
+
+    if (event in this.listeners) {
+      this.listeners[event].push(callback);
+    } else {
+      this.listeners[event] = [callback];
+    }
+  };
+
+  Observable.prototype.trigger = function (event) {
+    var slice = Array.prototype.slice;
+    var params = slice.call(arguments, 1);
+
+    this.listeners = this.listeners || {};
+
+    // Params should always come in as an array
+    if (params == null) {
+      params = [];
+    }
+
+    // If there are no arguments to the event, use a temporary object
+    if (params.length === 0) {
+      params.push({});
+    }
+
+    // Set the `_type` of the first object to the event
+    params[0]._type = event;
+
+    if (event in this.listeners) {
+      this.invoke(this.listeners[event], slice.call(arguments, 1));
+    }
+
+    if ('*' in this.listeners) {
+      this.invoke(this.listeners['*'], arguments);
+    }
+  };
+
+  Observable.prototype.invoke = function (listeners, params) {
+    for (var i = 0, len = listeners.length; i < len; i++) {
+      listeners[i].apply(this, params);
+    }
+  };
+
+  Utils.Observable = Observable;
+
+  Utils.generateChars = function (length) {
+    var chars = '';
+
+    for (var i = 0; i < length; i++) {
+      var randomChar = Math.floor(Math.random() * 36);
+      chars += randomChar.toString(36);
+    }
+
+    return chars;
+  };
+
+  Utils.bind = function (func, context) {
+    return function () {
+      func.apply(context, arguments);
+    };
+  };
+
+  Utils._convertData = function (data) {
+    for (var originalKey in data) {
+      var keys = originalKey.split('-');
+
+      var dataLevel = data;
+
+      if (keys.length === 1) {
+        continue;
+      }
+
+      for (var k = 0; k < keys.length; k++) {
+        var key = keys[k];
+
+        // Lowercase the first letter
+        // By default, dash-separated becomes camelCase
+        key = key.substring(0, 1).toLowerCase() + key.substring(1);
+
+        if (!(key in dataLevel)) {
+          dataLevel[key] = {};
+        }
+
+        if (k == keys.length - 1) {
+          dataLevel[key] = data[originalKey];
+        }
+
+        dataLevel = dataLevel[key];
+      }
+
+      delete data[originalKey];
+    }
+
+    return data;
+  };
+
+  Utils.hasScroll = function (index, el) {
+    // Adapted from the function created by @ShadowScripter
+    // and adapted by @BillBarry on the Stack Exchange Code Review website.
+    // The original code can be found at
+    // http://codereview.stackexchange.com/q/13338
+    // and was designed to be used with the Sizzle selector engine.
+
+    var $el = $(el);
+    var overflowX = el.style.overflowX;
+    var overflowY = el.style.overflowY;
+
+    //Check both x and y declarations
+    if (overflowX === overflowY &&
+        (overflowY === 'hidden' || overflowY === 'visible')) {
+      return false;
+    }
+
+    if (overflowX === 'scroll' || overflowY === 'scroll') {
+      return true;
+    }
+
+    return ($el.innerHeight() < el.scrollHeight ||
+      $el.innerWidth() < el.scrollWidth);
+  };
+
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    // Do not try to escape the markup if it's not a string
+    if (typeof markup !== 'string') {
+      return markup;
+    }
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
+  // Append an array of jQuery nodes to a given element.
+  Utils.appendMany = function ($element, $nodes) {
+    // jQuery 1.7.x does not support $.fn.append() with an array
+    // Fall back to a jQuery object collection using $.fn.add()
+    if ($.fn.jquery.substr(0, 3) === '1.7') {
+      var $jqNodes = $();
+
+      $.map($nodes, function (node) {
+        $jqNodes = $jqNodes.add(node);
+      });
+
+      $nodes = $jqNodes;
+    }
+
+    $element.append($nodes);
+  };
+
+  return Utils;
+});
+
+S2.define('select2/results',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Results ($element, options, dataAdapter) {
+    this.$element = $element;
+    this.data = dataAdapter;
+    this.options = options;
+
+    Results.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Results, Utils.Observable);
+
+  Results.prototype.render = function () {
+    var $results = $(
+      '<ul class="select2-results__options" role="tree"></ul>'
+    );
+
+    if (this.options.get('multiple')) {
+      $results.attr('aria-multiselectable', 'true');
+    }
+
+    this.$results = $results;
+
+    return $results;
+  };
+
+  Results.prototype.clear = function () {
+    this.$results.empty();
+  };
+
+  Results.prototype.displayMessage = function (params) {
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    this.clear();
+    this.hideLoading();
+
+    var $message = $(
+      '<li role="treeitem" aria-live="assertive"' +
+      ' class="select2-results__option"></li>'
+    );
+
+    var message = this.options.get('translations').get(params.message);
+
+    $message.append(
+      escapeMarkup(
+        message(params.args)
+      )
+    );
+
+    $message[0].className += ' select2-results__message';
+
+    this.$results.append($message);
+  };
+
+  Results.prototype.hideMessages = function () {
+    this.$results.find('.select2-results__message').remove();
+  };
+
+  Results.prototype.append = function (data) {
+    this.hideLoading();
+
+    var $options = [];
+
+    if (data.results == null || data.results.length === 0) {
+      if (this.$results.children().length === 0) {
+        this.trigger('results:message', {
+          message: 'noResults'
+        });
+      }
+
+      return;
+    }
+
+    data.results = this.sort(data.results);
+
+    for (var d = 0; d < data.results.length; d++) {
+      var item = data.results[d];
+
+      var $option = this.option(item);
+
+      $options.push($option);
+    }
+
+    this.$results.append($options);
+  };
+
+  Results.prototype.position = function ($results, $dropdown) {
+    var $resultsContainer = $dropdown.find('.select2-results');
+    $resultsContainer.append($results);
+  };
+
+  Results.prototype.sort = function (data) {
+    var sorter = this.options.get('sorter');
+
+    return sorter(data);
+  };
+
+  Results.prototype.highlightFirstItem = function () {
+    var $options = this.$results
+      .find('.select2-results__option[aria-selected]');
+
+    var $selected = $options.filter('[aria-selected=true]');
+
+    // Check if there are any selected options
+    if ($selected.length > 0) {
+      // If there are selected options, highlight the first
+      $selected.first().trigger('mouseenter');
+    } else {
+      // If there are no selected options, highlight the first option
+      // in the dropdown
+      $options.first().trigger('mouseenter');
+    }
+
+    this.ensureHighlightVisible();
+  };
+
+  Results.prototype.setClasses = function () {
+    var self = this;
+
+    this.data.current(function (selected) {
+      var selectedIds = $.map(selected, function (s) {
+        return s.id.toString();
+      });
+
+      var $options = self.$results
+        .find('.select2-results__option[aria-selected]');
+
+      $options.each(function () {
+        var $option = $(this);
+
+        var item = $.data(this, 'data');
+
+        // id needs to be converted to a string when comparing
+        var id = '' + item.id;
+
+        if ((item.element != null && item.element.selected) ||
+            (item.element == null && $.inArray(id, selectedIds) > -1)) {
+          $option.attr('aria-selected', 'true');
+        } else {
+          $option.attr('aria-selected', 'false');
+        }
+      });
+
+    });
+  };
+
+  Results.prototype.showLoading = function (params) {
+    this.hideLoading();
+
+    var loadingMore = this.options.get('translations').get('searching');
+
+    var loading = {
+      disabled: true,
+      loading: true,
+      text: loadingMore(params)
+    };
+    var $loading = this.option(loading);
+    $loading.className += ' loading-results';
+
+    this.$results.prepend($loading);
+  };
+
+  Results.prototype.hideLoading = function () {
+    this.$results.find('.loading-results').remove();
+  };
+
+  Results.prototype.option = function (data) {
+    var option = document.createElement('li');
+    option.className = 'select2-results__option';
+
+    var attrs = {
+      'role': 'treeitem',
+      'aria-selected': 'false'
+    };
+
+    if (data.disabled) {
+      delete attrs['aria-selected'];
+      attrs['aria-disabled'] = 'true';
+    }
+
+    if (data.id == null) {
+      delete attrs['aria-selected'];
+    }
+
+    if (data._resultId != null) {
+      option.id = data._resultId;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    if (data.children) {
+      attrs.role = 'group';
+      attrs['aria-label'] = data.text;
+      delete attrs['aria-selected'];
+    }
+
+    for (var attr in attrs) {
+      var val = attrs[attr];
+
+      option.setAttribute(attr, val);
+    }
+
+    if (data.children) {
+      var $option = $(option);
+
+      var label = document.createElement('strong');
+      label.className = 'select2-results__group';
+
+      var $label = $(label);
+      this.template(data, label);
+
+      var $children = [];
+
+      for (var c = 0; c < data.children.length; c++) {
+        var child = data.children[c];
+
+        var $child = this.option(child);
+
+        $children.push($child);
+      }
+
+      var $childrenContainer = $('<ul></ul>', {
+        'class': 'select2-results__options select2-results__options--nested'
+      });
+
+      $childrenContainer.append($children);
+
+      $option.append(label);
+      $option.append($childrenContainer);
+    } else {
+      this.template(data, option);
+    }
+
+    $.data(option, 'data', data);
+
+    return option;
+  };
+
+  Results.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-results';
+
+    this.$results.attr('id', id);
+
+    container.on('results:all', function (params) {
+      self.clear();
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+        self.highlightFirstItem();
+      }
+    });
+
+    container.on('results:append', function (params) {
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+      }
+    });
+
+    container.on('query', function (params) {
+      self.hideMessages();
+      self.showLoading(params);
+    });
+
+    container.on('select', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+      self.highlightFirstItem();
+    });
+
+    container.on('unselect', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+      self.highlightFirstItem();
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expended="true"
+      self.$results.attr('aria-expanded', 'true');
+      self.$results.attr('aria-hidden', 'false');
+
+      self.setClasses();
+      self.ensureHighlightVisible();
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expended="false"
+      self.$results.attr('aria-expanded', 'false');
+      self.$results.attr('aria-hidden', 'true');
+      self.$results.removeAttr('aria-activedescendant');
+    });
+
+    container.on('results:toggle', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      $highlighted.trigger('mouseup');
+    });
+
+    container.on('results:select', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      var data = $highlighted.data('data');
+
+      if ($highlighted.attr('aria-selected') == 'true') {
+        self.trigger('close', {});
+      } else {
+        self.trigger('select', {
+          data: data
+        });
+      }
+    });
+
+    container.on('results:previous', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      // If we are already at te top, don't move further
+      if (currentIndex === 0) {
+        return;
+      }
+
+      var nextIndex = currentIndex - 1;
+
+      // If none are highlighted, highlight the first
+      if ($highlighted.length === 0) {
+        nextIndex = 0;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top;
+      var nextTop = $next.offset().top;
+      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextTop - currentOffset < 0) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:next', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      var nextIndex = currentIndex + 1;
+
+      // If we are at the last option, stay there
+      if (nextIndex >= $options.length) {
+        return;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var nextBottom = $next.offset().top + $next.outerHeight(false);
+      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextBottom > currentOffset) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      params.element.addClass('select2-results__option--highlighted');
+    });
+
+    container.on('results:message', function (params) {
+      self.displayMessage(params);
+    });
+
+    if ($.fn.mousewheel) {
+      this.$results.on('mousewheel', function (e) {
+        var top = self.$results.scrollTop();
+
+        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
+
+        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
+        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
+
+        if (isAtTop) {
+          self.$results.scrollTop(0);
+
+          e.preventDefault();
+          e.stopPropagation();
+        } else if (isAtBottom) {
+          self.$results.scrollTop(
+            self.$results.get(0).scrollHeight - self.$results.height()
+          );
+
+          e.preventDefault();
+          e.stopPropagation();
+        }
+      });
+    }
+
+    this.$results.on('mouseup', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var $this = $(this);
+
+      var data = $this.data('data');
+
+      if ($this.attr('aria-selected') === 'true') {
+        if (self.options.get('multiple')) {
+          self.trigger('unselect', {
+            originalEvent: evt,
+            data: data
+          });
+        } else {
+          self.trigger('close', {});
+        }
+
+        return;
+      }
+
+      self.trigger('select', {
+        originalEvent: evt,
+        data: data
+      });
+    });
+
+    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var data = $(this).data('data');
+
+      self.getHighlightedResults()
+          .removeClass('select2-results__option--highlighted');
+
+      self.trigger('results:focus', {
+        data: data,
+        element: $(this)
+      });
+    });
+  };
+
+  Results.prototype.getHighlightedResults = function () {
+    var $highlighted = this.$results
+    .find('.select2-results__option--highlighted');
+
+    return $highlighted;
+  };
+
+  Results.prototype.destroy = function () {
+    this.$results.remove();
+  };
+
+  Results.prototype.ensureHighlightVisible = function () {
+    var $highlighted = this.getHighlightedResults();
+
+    if ($highlighted.length === 0) {
+      return;
+    }
+
+    var $options = this.$results.find('[aria-selected]');
+
+    var currentIndex = $options.index($highlighted);
+
+    var currentOffset = this.$results.offset().top;
+    var nextTop = $highlighted.offset().top;
+    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
+
+    var offsetDelta = nextTop - currentOffset;
+    nextOffset -= $highlighted.outerHeight(false) * 2;
+
+    if (currentIndex <= 2) {
+      this.$results.scrollTop(0);
+    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
+      this.$results.scrollTop(nextOffset);
+    }
+  };
+
+  Results.prototype.template = function (result, container) {
+    var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    var content = template(result, container);
+
+    if (content == null) {
+      container.style.display = 'none';
+    } else if (typeof content === 'string') {
+      container.innerHTML = escapeMarkup(content);
+    } else {
+      $(container).append(content);
+    }
+  };
+
+  return Results;
+});
+
+S2.define('select2/keys',[
+
+], function () {
+  var KEYS = {
+    BACKSPACE: 8,
+    TAB: 9,
+    ENTER: 13,
+    SHIFT: 16,
+    CTRL: 17,
+    ALT: 18,
+    ESC: 27,
+    SPACE: 32,
+    PAGE_UP: 33,
+    PAGE_DOWN: 34,
+    END: 35,
+    HOME: 36,
+    LEFT: 37,
+    UP: 38,
+    RIGHT: 39,
+    DOWN: 40,
+    DELETE: 46
+  };
+
+  return KEYS;
+});
+
+S2.define('select2/selection/base',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function BaseSelection ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    BaseSelection.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseSelection, Utils.Observable);
+
+  BaseSelection.prototype.render = function () {
+    var $selection = $(
+      '<span class="select2-selection" role="combobox" ' +
+      ' aria-haspopup="true" aria-expanded="false">' +
+      '</span>'
+    );
+
+    this._tabindex = 0;
+
+    if (this.$element.data('old-tabindex') != null) {
+      this._tabindex = this.$element.data('old-tabindex');
+    } else if (this.$element.attr('tabindex') != null) {
+      this._tabindex = this.$element.attr('tabindex');
+    }
+
+    $selection.attr('title', this.$element.attr('title'));
+    $selection.attr('tabindex', this._tabindex);
+
+    this.$selection = $selection;
+
+    return $selection;
+  };
+
+  BaseSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-container';
+    var resultsId = container.id + '-results';
+
+    this.container = container;
+
+    this.$selection.on('focus', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('blur', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      if (evt.which === KEYS.SPACE) {
+        evt.preventDefault();
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      self.$selection.attr('aria-activedescendant', params.data._resultId);
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expanded="true"
+      self.$selection.attr('aria-expanded', 'true');
+      self.$selection.attr('aria-owns', resultsId);
+
+      self._attachCloseHandler(container);
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expanded="false"
+      self.$selection.attr('aria-expanded', 'false');
+      self.$selection.removeAttr('aria-activedescendant');
+      self.$selection.removeAttr('aria-owns');
+
+      self.$selection.focus();
+
+      self._detachCloseHandler(container);
+    });
+
+    container.on('enable', function () {
+      self.$selection.attr('tabindex', self._tabindex);
+    });
+
+    container.on('disable', function () {
+      self.$selection.attr('tabindex', '-1');
+    });
+  };
+
+  BaseSelection.prototype._handleBlur = function (evt) {
+    var self = this;
+
+    // This needs to be delayed as the active element is the body when the tab
+    // key is pressed, possibly along with others.
+    window.setTimeout(function () {
+      // Don't trigger `blur` if the focus is still in the selection
+      if (
+        (document.activeElement == self.$selection[0]) ||
+        ($.contains(self.$selection[0], document.activeElement))
+      ) {
+        return;
+      }
+
+      self.trigger('blur', evt);
+    }, 1);
+  };
+
+  BaseSelection.prototype._attachCloseHandler = function (container) {
+    var self = this;
+
+    $(document.body).on('mousedown.select2.' + container.id, function (e) {
+      var $target = $(e.target);
+
+      var $select = $target.closest('.select2');
+
+      var $all = $('.select2.select2-container--open');
+
+      $all.each(function () {
+        var $this = $(this);
+
+        if (this == $select[0]) {
+          return;
+        }
+
+        var $element = $this.data('element');
+
+        $element.select2('close');
+      });
+    });
+  };
+
+  BaseSelection.prototype._detachCloseHandler = function (container) {
+    $(document.body).off('mousedown.select2.' + container.id);
+  };
+
+  BaseSelection.prototype.position = function ($selection, $container) {
+    var $selectionContainer = $container.find('.selection');
+    $selectionContainer.append($selection);
+  };
+
+  BaseSelection.prototype.destroy = function () {
+    this._detachCloseHandler(this.container);
+  };
+
+  BaseSelection.prototype.update = function (data) {
+    throw new Error('The `update` method must be defined in child classes.');
+  };
+
+  return BaseSelection;
+});
+
+S2.define('select2/selection/single',[
+  'jquery',
+  './base',
+  '../utils',
+  '../keys'
+], function ($, BaseSelection, Utils, KEYS) {
+  function SingleSelection () {
+    SingleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(SingleSelection, BaseSelection);
+
+  SingleSelection.prototype.render = function () {
+    var $selection = SingleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--single');
+
+    $selection.html(
+      '<span class="select2-selection__rendered"></span>' +
+      '<span class="select2-selection__arrow" role="presentation">' +
+        '<b role="presentation"></b>' +
+      '</span>'
+    );
+
+    return $selection;
+  };
+
+  SingleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    SingleSelection.__super__.bind.apply(this, arguments);
+
+    var id = container.id + '-container';
+
+    this.$selection.find('.select2-selection__rendered').attr('id', id);
+    this.$selection.attr('aria-labelledby', id);
+
+    this.$selection.on('mousedown', function (evt) {
+      // Only respond to left clicks
+      if (evt.which !== 1) {
+        return;
+      }
+
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on('focus', function (evt) {
+      // User focuses on the container
+    });
+
+    this.$selection.on('blur', function (evt) {
+      // User exits the container
+    });
+
+    container.on('focus', function (evt) {
+      if (!container.isOpen()) {
+        self.$selection.focus();
+      }
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+  };
+
+  SingleSelection.prototype.clear = function () {
+    this.$selection.find('.select2-selection__rendered').empty();
+  };
+
+  SingleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  SingleSelection.prototype.selectionContainer = function () {
+    return $('<span></span>');
+  };
+
+  SingleSelection.prototype.update = function (data) {
+    if (data.length === 0) {
+      this.clear();
+      return;
+    }
+
+    var selection = data[0];
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+    var formatted = this.display(selection, $rendered);
+
+    $rendered.empty().append(formatted);
+    $rendered.prop('title', selection.title || selection.text);
+  };
+
+  return SingleSelection;
+});
+
+S2.define('select2/selection/multiple',[
+  'jquery',
+  './base',
+  '../utils'
+], function ($, BaseSelection, Utils) {
+  function MultipleSelection ($element, options) {
+    MultipleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(MultipleSelection, BaseSelection);
+
+  MultipleSelection.prototype.render = function () {
+    var $selection = MultipleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--multiple');
+
+    $selection.html(
+      '<ul class="select2-selection__rendered"></ul>'
+    );
+
+    return $selection;
+  };
+
+  MultipleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    MultipleSelection.__super__.bind.apply(this, arguments);
+
+    this.$selection.on('click', function (evt) {
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on(
+      'click',
+      '.select2-selection__choice__remove',
+      function (evt) {
+        // Ignore the event if it is disabled
+        if (self.options.get('disabled')) {
+          return;
+        }
+
+        var $remove = $(this);
+        var $selection = $remove.parent();
+
+        var data = $selection.data('data');
+
+        self.trigger('unselect', {
+          originalEvent: evt,
+          data: data
+        });
+      }
+    );
+  };
+
+  MultipleSelection.prototype.clear = function () {
+    this.$selection.find('.select2-selection__rendered').empty();
+  };
+
+  MultipleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  MultipleSelection.prototype.selectionContainer = function () {
+    var $container = $(
+      '<li class="select2-selection__choice">' +
+        '<span class="select2-selection__choice__remove" role="presentation">' +
+          '&times;' +
+        '</span>' +
+      '</li>'
+    );
+
+    return $container;
+  };
+
+  MultipleSelection.prototype.update = function (data) {
+    this.clear();
+
+    if (data.length === 0) {
+      return;
+    }
+
+    var $selections = [];
+
+    for (var d = 0; d < data.length; d++) {
+      var selection = data[d];
+
+      var $selection = this.selectionContainer();
+      var formatted = this.display(selection, $selection);
+
+      $selection.append(formatted);
+      $selection.prop('title', selection.title || selection.text);
+
+      $selection.data('data', selection);
+
+      $selections.push($selection);
+    }
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+
+    Utils.appendMany($rendered, $selections);
+  };
+
+  return MultipleSelection;
+});
+
+S2.define('select2/selection/placeholder',[
+  '../utils'
+], function (Utils) {
+  function Placeholder (decorated, $element, options) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options);
+  }
+
+  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
+    var $placeholder = this.selectionContainer();
+
+    $placeholder.html(this.display(placeholder));
+    $placeholder.addClass('select2-selection__placeholder')
+                .removeClass('select2-selection__choice');
+
+    return $placeholder;
+  };
+
+  Placeholder.prototype.update = function (decorated, data) {
+    var singlePlaceholder = (
+      data.length == 1 && data[0].id != this.placeholder.id
+    );
+    var multipleSelections = data.length > 1;
+
+    if (multipleSelections || singlePlaceholder) {
+      return decorated.call(this, data);
+    }
+
+    this.clear();
+
+    var $placeholder = this.createPlaceholder(this.placeholder);
+
+    this.$selection.find('.select2-selection__rendered').append($placeholder);
+  };
+
+  return Placeholder;
+});
+
+S2.define('select2/selection/allowClear',[
+  'jquery',
+  '../keys'
+], function ($, KEYS) {
+  function AllowClear () { }
+
+  AllowClear.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    if (this.placeholder == null) {
+      if (this.options.get('debug') && window.console && console.error) {
+        console.error(
+          'Select2: The `allowClear` option should be used in combination ' +
+          'with the `placeholder` option.'
+        );
+      }
+    }
+
+    this.$selection.on('mousedown', '.select2-selection__clear',
+      function (evt) {
+        self._handleClear(evt);
+    });
+
+    container.on('keypress', function (evt) {
+      self._handleKeyboardClear(evt, container);
+    });
+  };
+
+  AllowClear.prototype._handleClear = function (_, evt) {
+    // Ignore the event if it is disabled
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    var $clear = this.$selection.find('.select2-selection__clear');
+
+    // Ignore the event if nothing has been selected
+    if ($clear.length === 0) {
+      return;
+    }
+
+    evt.stopPropagation();
+
+    var data = $clear.data('data');
+
+    for (var d = 0; d < data.length; d++) {
+      var unselectData = {
+        data: data[d]
+      };
+
+      // Trigger the `unselect` event, so people can prevent it from being
+      // cleared.
+      this.trigger('unselect', unselectData);
+
+      // If the event was prevented, don't clear it out.
+      if (unselectData.prevented) {
+        return;
+      }
+    }
+
+    this.$element.val(this.placeholder.id).trigger('change');
+
+    this.trigger('toggle', {});
+  };
+
+  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
+    if (container.isOpen()) {
+      return;
+    }
+
+    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
+      this._handleClear(evt);
+    }
+  };
+
+  AllowClear.prototype.update = function (decorated, data) {
+    decorated.call(this, data);
+
+    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
+        data.length === 0) {
+      return;
+    }
+
+    var $remove = $(
+      '<span class="select2-selection__clear">' +
+        '&times;' +
+      '</span>'
+    );
+    $remove.data('data', data);
+
+    this.$selection.find('.select2-selection__rendered').prepend($remove);
+  };
+
+  return AllowClear;
+});
+
+S2.define('select2/selection/search',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function Search (decorated, $element, options) {
+    decorated.call(this, $element, options);
+  }
+
+  Search.prototype.render = function (decorated) {
+    var $search = $(
+      '<li class="select2-search select2-search--inline">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
+      '</li>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    var $rendered = decorated.call(this);
+
+    this._transferTabIndex();
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self.$search.trigger('focus');
+    });
+
+    container.on('close', function () {
+      self.$search.val('');
+      self.$search.removeAttr('aria-activedescendant');
+      self.$search.trigger('focus');
+    });
+
+    container.on('enable', function () {
+      self.$search.prop('disabled', false);
+
+      self._transferTabIndex();
+    });
+
+    container.on('disable', function () {
+      self.$search.prop('disabled', true);
+    });
+
+    container.on('focus', function (evt) {
+      self.$search.trigger('focus');
+    });
+
+    container.on('results:focus', function (params) {
+      self.$search.attr('aria-activedescendant', params.id);
+    });
+
+    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
+      evt.stopPropagation();
+
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+
+      var key = evt.which;
+
+      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
+        var $previousChoice = self.$searchContainer
+          .prev('.select2-selection__choice');
+
+        if ($previousChoice.length > 0) {
+          var item = $previousChoice.data('data');
+
+          self.searchRemoveChoice(item);
+
+          evt.preventDefault();
+        }
+      }
+    });
+
+    // Try to detect the IE version should the `documentMode` property that
+    // is stored on the document. This is only implemented in IE and is
+    // slightly cleaner than doing a user agent check.
+    // This property is not available in Edge, but Edge also doesn't have
+    // this bug.
+    var msie = document.documentMode;
+    var disableInputEvents = msie && msie <= 11;
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$selection.on(
+      'input.searchcheck',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents) {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        // Unbind the duplicated `keyup` event
+        self.$selection.off('keyup.search');
+      }
+    );
+
+    this.$selection.on(
+      'keyup.search input.search',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents && evt.type === 'input') {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        var key = evt.which;
+
+        // We can freely ignore events from modifier keys
+        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
+          return;
+        }
+
+        // Tabbing will be handled during the `keydown` phase
+        if (key == KEYS.TAB) {
+          return;
+        }
+
+        self.handleSearch(evt);
+      }
+    );
+  };
+
+  /**
+   * This method will transfer the tabindex attribute from the rendered
+   * selection to the search box. This allows for the search box to be used as
+   * the primary focus instead of the selection container.
+   *
+   * @private
+   */
+  Search.prototype._transferTabIndex = function (decorated) {
+    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
+    this.$selection.attr('tabindex', '-1');
+  };
+
+  Search.prototype.createPlaceholder = function (decorated, placeholder) {
+    this.$search.attr('placeholder', placeholder.text);
+  };
+
+  Search.prototype.update = function (decorated, data) {
+    var searchHadFocus = this.$search[0] == document.activeElement;
+
+    this.$search.attr('placeholder', '');
+
+    decorated.call(this, data);
+
+    this.$selection.find('.select2-selection__rendered')
+                   .append(this.$searchContainer);
+
+    this.resizeSearch();
+    if (searchHadFocus) {
+      this.$search.focus();
+    }
+  };
+
+  Search.prototype.handleSearch = function () {
+    this.resizeSearch();
+
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.searchRemoveChoice = function (decorated, item) {
+    this.trigger('unselect', {
+      data: item
+    });
+
+    this.$search.val(item.text);
+    this.handleSearch();
+  };
+
+  Search.prototype.resizeSearch = function () {
+    this.$search.css('width', '25px');
+
+    var width = '';
+
+    if (this.$search.attr('placeholder') !== '') {
+      width = this.$selection.find('.select2-selection__rendered').innerWidth();
+    } else {
+      var minimumWidth = this.$search.val().length + 1;
+
+      width = (minimumWidth * 0.75) + 'em';
+    }
+
+    this.$search.css('width', width);
+  };
+
+  return Search;
+});
+
+S2.define('select2/selection/eventRelay',[
+  'jquery'
+], function ($) {
+  function EventRelay () { }
+
+  EventRelay.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+    var relayEvents = [
+      'open', 'opening',
+      'close', 'closing',
+      'select', 'selecting',
+      'unselect', 'unselecting'
+    ];
+
+    var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
+
+    decorated.call(this, container, $container);
+
+    container.on('*', function (name, params) {
+      // Ignore events that should not be relayed
+      if ($.inArray(name, relayEvents) === -1) {
+        return;
+      }
+
+      // The parameters should always be an object
+      params = params || {};
+
+      // Generate the jQuery event for the Select2 event
+      var evt = $.Event('select2:' + name, {
+        params: params
+      });
+
+      self.$element.trigger(evt);
+
+      // Only handle preventable events if it was one
+      if ($.inArray(name, preventableEvents) === -1) {
+        return;
+      }
+
+      params.prevented = evt.isDefaultPrevented();
+    });
+  };
+
+  return EventRelay;
+});
+
+S2.define('select2/translation',[
+  'jquery',
+  'require'
+], function ($, require) {
+  function Translation (dict) {
+    this.dict = dict || {};
+  }
+
+  Translation.prototype.all = function () {
+    return this.dict;
+  };
+
+  Translation.prototype.get = function (key) {
+    return this.dict[key];
+  };
+
+  Translation.prototype.extend = function (translation) {
+    this.dict = $.extend({}, translation.all(), this.dict);
+  };
+
+  // Static functions
+
+  Translation._cache = {};
+
+  Translation.loadPath = function (path) {
+    if (!(path in Translation._cache)) {
+      var translations = require(path);
+
+      Translation._cache[path] = translations;
+    }
+
+    return new Translation(Translation._cache[path]);
+  };
+
+  return Translation;
+});
+
+S2.define('select2/diacritics',[
+
+], function () {
+  var diacritics = {
+    '\u24B6': 'A',
+    '\uFF21': 'A',
+    '\u00C0': 'A',
+    '\u00C1': 'A',
+    '\u00C2': 'A',
+    '\u1EA6': 'A',
+    '\u1EA4': 'A',
+    '\u1EAA': 'A',
+    '\u1EA8': 'A',
+    '\u00C3': 'A',
+    '\u0100': 'A',
+    '\u0102': 'A',
+    '\u1EB0': 'A',
+    '\u1EAE': 'A',
+    '\u1EB4': 'A',
+    '\u1EB2': 'A',
+    '\u0226': 'A',
+    '\u01E0': 'A',
+    '\u00C4': 'A',
+    '\u01DE': 'A',
+    '\u1EA2': 'A',
+    '\u00C5': 'A',
+    '\u01FA': 'A',
+    '\u01CD': 'A',
+    '\u0200': 'A',
+    '\u0202': 'A',
+    '\u1EA0': 'A',
+    '\u1EAC': 'A',
+    '\u1EB6': 'A',
+    '\u1E00': 'A',
+    '\u0104': 'A',
+    '\u023A': 'A',
+    '\u2C6F': 'A',
+    '\uA732': 'AA',
+    '\u00C6': 'AE',
+    '\u01FC': 'AE',
+    '\u01E2': 'AE',
+    '\uA734': 'AO',
+    '\uA736': 'AU',
+    '\uA738': 'AV',
+    '\uA73A': 'AV',
+    '\uA73C': 'AY',
+    '\u24B7': 'B',
+    '\uFF22': 'B',
+    '\u1E02': 'B',
+    '\u1E04': 'B',
+    '\u1E06': 'B',
+    '\u0243': 'B',
+    '\u0182': 'B',
+    '\u0181': 'B',
+    '\u24B8': 'C',
+    '\uFF23': 'C',
+    '\u0106': 'C',
+    '\u0108': 'C',
+    '\u010A': 'C',
+    '\u010C': 'C',
+    '\u00C7': 'C',
+    '\u1E08': 'C',
+    '\u0187': 'C',
+    '\u023B': 'C',
+    '\uA73E': 'C',
+    '\u24B9': 'D',
+    '\uFF24': 'D',
+    '\u1E0A': 'D',
+    '\u010E': 'D',
+    '\u1E0C': 'D',
+    '\u1E10': 'D',
+    '\u1E12': 'D',
+    '\u1E0E': 'D',
+    '\u0110': 'D',
+    '\u018B': 'D',
+    '\u018A': 'D',
+    '\u0189': 'D',
+    '\uA779': 'D',
+    '\u01F1': 'DZ',
+    '\u01C4': 'DZ',
+    '\u01F2': 'Dz',
+    '\u01C5': 'Dz',
+    '\u24BA': 'E',
+    '\uFF25': 'E',
+    '\u00C8': 'E',
+    '\u00C9': 'E',
+    '\u00CA': 'E',
+    '\u1EC0': 'E',
+    '\u1EBE': 'E',
+    '\u1EC4': 'E',
+    '\u1EC2': 'E',
+    '\u1EBC': 'E',
+    '\u0112': 'E',
+    '\u1E14': 'E',
+    '\u1E16': 'E',
+    '\u0114': 'E',
+    '\u0116': 'E',
+    '\u00CB': 'E',
+    '\u1EBA': 'E',
+    '\u011A': 'E',
+    '\u0204': 'E',
+    '\u0206': 'E',
+    '\u1EB8': 'E',
+    '\u1EC6': 'E',
+    '\u0228': 'E',
+    '\u1E1C': 'E',
+    '\u0118': 'E',
+    '\u1E18': 'E',
+    '\u1E1A': 'E',
+    '\u0190': 'E',
+    '\u018E': 'E',
+    '\u24BB': 'F',
+    '\uFF26': 'F',
+    '\u1E1E': 'F',
+    '\u0191': 'F',
+    '\uA77B': 'F',
+    '\u24BC': 'G',
+    '\uFF27': 'G',
+    '\u01F4': 'G',
+    '\u011C': 'G',
+    '\u1E20': 'G',
+    '\u011E': 'G',
+    '\u0120': 'G',
+    '\u01E6': 'G',
+    '\u0122': 'G',
+    '\u01E4': 'G',
+    '\u0193': 'G',
+    '\uA7A0': 'G',
+    '\uA77D': 'G',
+    '\uA77E': 'G',
+    '\u24BD': 'H',
+    '\uFF28': 'H',
+    '\u0124': 'H',
+    '\u1E22': 'H',
+    '\u1E26': 'H',
+    '\u021E': 'H',
+    '\u1E24': 'H',
+    '\u1E28': 'H',
+    '\u1E2A': 'H',
+    '\u0126': 'H',
+    '\u2C67': 'H',
+    '\u2C75': 'H',
+    '\uA78D': 'H',
+    '\u24BE': 'I',
+    '\uFF29': 'I',
+    '\u00CC': 'I',
+    '\u00CD': 'I',
+    '\u00CE': 'I',
+    '\u0128': 'I',
+    '\u012A': 'I',
+    '\u012C': 'I',
+    '\u0130': 'I',
+    '\u00CF': 'I',
+    '\u1E2E': 'I',
+    '\u1EC8': 'I',
+    '\u01CF': 'I',
+    '\u0208': 'I',
+    '\u020A': 'I',
+    '\u1ECA': 'I',
+    '\u012E': 'I',
+    '\u1E2C': 'I',
+    '\u0197': 'I',
+    '\u24BF': 'J',
+    '\uFF2A': 'J',
+    '\u0134': 'J',
+    '\u0248': 'J',
+    '\u24C0': 'K',
+    '\uFF2B': 'K',
+    '\u1E30': 'K',
+    '\u01E8': 'K',
+    '\u1E32': 'K',
+    '\u0136': 'K',
+    '\u1E34': 'K',
+    '\u0198': 'K',
+    '\u2C69': 'K',
+    '\uA740': 'K',
+    '\uA742': 'K',
+    '\uA744': 'K',
+    '\uA7A2': 'K',
+    '\u24C1': 'L',
+    '\uFF2C': 'L',
+    '\u013F': 'L',
+    '\u0139': 'L',
+    '\u013D': 'L',
+    '\u1E36': 'L',
+    '\u1E38': 'L',
+    '\u013B': 'L',
+    '\u1E3C': 'L',
+    '\u1E3A': 'L',
+    '\u0141': 'L',
+    '\u023D': 'L',
+    '\u2C62': 'L',
+    '\u2C60': 'L',
+    '\uA748': 'L',
+    '\uA746': 'L',
+    '\uA780': 'L',
+    '\u01C7': 'LJ',
+    '\u01C8': 'Lj',
+    '\u24C2': 'M',
+    '\uFF2D': 'M',
+    '\u1E3E': 'M',
+    '\u1E40': 'M',
+    '\u1E42': 'M',
+    '\u2C6E': 'M',
+    '\u019C': 'M',
+    '\u24C3': 'N',
+    '\uFF2E': 'N',
+    '\u01F8': 'N',
+    '\u0143': 'N',
+    '\u00D1': 'N',
+    '\u1E44': 'N',
+    '\u0147': 'N',
+    '\u1E46': 'N',
+    '\u0145': 'N',
+    '\u1E4A': 'N',
+    '\u1E48': 'N',
+    '\u0220': 'N',
+    '\u019D': 'N',
+    '\uA790': 'N',
+    '\uA7A4': 'N',
+    '\u01CA': 'NJ',
+    '\u01CB': 'Nj',
+    '\u24C4': 'O',
+    '\uFF2F': 'O',
+    '\u00D2': 'O',
+    '\u00D3': 'O',
+    '\u00D4': 'O',
+    '\u1ED2': 'O',
+    '\u1ED0': 'O',
+    '\u1ED6': 'O',
+    '\u1ED4': 'O',
+    '\u00D5': 'O',
+    '\u1E4C': 'O',
+    '\u022C': 'O',
+    '\u1E4E': 'O',
+    '\u014C': 'O',
+    '\u1E50': 'O',
+    '\u1E52': 'O',
+    '\u014E': 'O',
+    '\u022E': 'O',
+    '\u0230': 'O',
+    '\u00D6': 'O',
+    '\u022A': 'O',
+    '\u1ECE': 'O',
+    '\u0150': 'O',
+    '\u01D1': 'O',
+    '\u020C': 'O',
+    '\u020E': 'O',
+    '\u01A0': 'O',
+    '\u1EDC': 'O',
+    '\u1EDA': 'O',
+    '\u1EE0': 'O',
+    '\u1EDE': 'O',
+    '\u1EE2': 'O',
+    '\u1ECC': 'O',
+    '\u1ED8': 'O',
+    '\u01EA': 'O',
+    '\u01EC': 'O',
+    '\u00D8': 'O',
+    '\u01FE': 'O',
+    '\u0186': 'O',
+    '\u019F': 'O',
+    '\uA74A': 'O',
+    '\uA74C': 'O',
+    '\u01A2': 'OI',
+    '\uA74E': 'OO',
+    '\u0222': 'OU',
+    '\u24C5': 'P',
+    '\uFF30': 'P',
+    '\u1E54': 'P',
+    '\u1E56': 'P',
+    '\u01A4': 'P',
+    '\u2C63': 'P',
+    '\uA750': 'P',
+    '\uA752': 'P',
+    '\uA754': 'P',
+    '\u24C6': 'Q',
+    '\uFF31': 'Q',
+    '\uA756': 'Q',
+    '\uA758': 'Q',
+    '\u024A': 'Q',
+    '\u24C7': 'R',
+    '\uFF32': 'R',
+    '\u0154': 'R',
+    '\u1E58': 'R',
+    '\u0158': 'R',
+    '\u0210': 'R',
+    '\u0212': 'R',
+    '\u1E5A': 'R',
+    '\u1E5C': 'R',
+    '\u0156': 'R',
+    '\u1E5E': 'R',
+    '\u024C': 'R',
+    '\u2C64': 'R',
+    '\uA75A': 'R',
+    '\uA7A6': 'R',
+    '\uA782': 'R',
+    '\u24C8': 'S',
+    '\uFF33': 'S',
+    '\u1E9E': 'S',
+    '\u015A': 'S',
+    '\u1E64': 'S',
+    '\u015C': 'S',
+    '\u1E60': 'S',
+    '\u0160': 'S',
+    '\u1E66': 'S',
+    '\u1E62': 'S',
+    '\u1E68': 'S',
+    '\u0218': 'S',
+    '\u015E': 'S',
+    '\u2C7E': 'S',
+    '\uA7A8': 'S',
+    '\uA784': 'S',
+    '\u24C9': 'T',
+    '\uFF34': 'T',
+    '\u1E6A': 'T',
+    '\u0164': 'T',
+    '\u1E6C': 'T',
+    '\u021A': 'T',
+    '\u0162': 'T',
+    '\u1E70': 'T',
+    '\u1E6E': 'T',
+    '\u0166': 'T',
+    '\u01AC': 'T',
+    '\u01AE': 'T',
+    '\u023E': 'T',
+    '\uA786': 'T',
+    '\uA728': 'TZ',
+    '\u24CA': 'U',
+    '\uFF35': 'U',
+    '\u00D9': 'U',
+    '\u00DA': 'U',
+    '\u00DB': 'U',
+    '\u0168': 'U',
+    '\u1E78': 'U',
+    '\u016A': 'U',
+    '\u1E7A': 'U',
+    '\u016C': 'U',
+    '\u00DC': 'U',
+    '\u01DB': 'U',
+    '\u01D7': 'U',
+    '\u01D5': 'U',
+    '\u01D9': 'U',
+    '\u1EE6': 'U',
+    '\u016E': 'U',
+    '\u0170': 'U',
+    '\u01D3': 'U',
+    '\u0214': 'U',
+    '\u0216': 'U',
+    '\u01AF': 'U',
+    '\u1EEA': 'U',
+    '\u1EE8': 'U',
+    '\u1EEE': 'U',
+    '\u1EEC': 'U',
+    '\u1EF0': 'U',
+    '\u1EE4': 'U',
+    '\u1E72': 'U',
+    '\u0172': 'U',
+    '\u1E76': 'U',
+    '\u1E74': 'U',
+    '\u0244': 'U',
+    '\u24CB': 'V',
+    '\uFF36': 'V',
+    '\u1E7C': 'V',
+    '\u1E7E': 'V',
+    '\u01B2': 'V',
+    '\uA75E': 'V',
+    '\u0245': 'V',
+    '\uA760': 'VY',
+    '\u24CC': 'W',
+    '\uFF37': 'W',
+    '\u1E80': 'W',
+    '\u1E82': 'W',
+    '\u0174': 'W',
+    '\u1E86': 'W',
+    '\u1E84': 'W',
+    '\u1E88': 'W',
+    '\u2C72': 'W',
+    '\u24CD': 'X',
+    '\uFF38': 'X',
+    '\u1E8A': 'X',
+    '\u1E8C': 'X',
+    '\u24CE': 'Y',
+    '\uFF39': 'Y',
+    '\u1EF2': 'Y',
+    '\u00DD': 'Y',
+    '\u0176': 'Y',
+    '\u1EF8': 'Y',
+    '\u0232': 'Y',
+    '\u1E8E': 'Y',
+    '\u0178': 'Y',
+    '\u1EF6': 'Y',
+    '\u1EF4': 'Y',
+    '\u01B3': 'Y',
+    '\u024E': 'Y',
+    '\u1EFE': 'Y',
+    '\u24CF': 'Z',
+    '\uFF3A': 'Z',
+    '\u0179': 'Z',
+    '\u1E90': 'Z',
+    '\u017B': 'Z',
+    '\u017D': 'Z',
+    '\u1E92': 'Z',
+    '\u1E94': 'Z',
+    '\u01B5': 'Z',
+    '\u0224': 'Z',
+    '\u2C7F': 'Z',
+    '\u2C6B': 'Z',
+    '\uA762': 'Z',
+    '\u24D0': 'a',
+    '\uFF41': 'a',
+    '\u1E9A': 'a',
+    '\u00E0': 'a',
+    '\u00E1': 'a',
+    '\u00E2': 'a',
+    '\u1EA7': 'a',
+    '\u1EA5': 'a',
+    '\u1EAB': 'a',
+    '\u1EA9': 'a',
+    '\u00E3': 'a',
+    '\u0101': 'a',
+    '\u0103': 'a',
+    '\u1EB1': 'a',
+    '\u1EAF': 'a',
+    '\u1EB5': 'a',
+    '\u1EB3': 'a',
+    '\u0227': 'a',
+    '\u01E1': 'a',
+    '\u00E4': 'a',
+    '\u01DF': 'a',
+    '\u1EA3': 'a',
+    '\u00E5': 'a',
+    '\u01FB': 'a',
+    '\u01CE': 'a',
+    '\u0201': 'a',
+    '\u0203': 'a',
+    '\u1EA1': 'a',
+    '\u1EAD': 'a',
+    '\u1EB7': 'a',
+    '\u1E01': 'a',
+    '\u0105': 'a',
+    '\u2C65': 'a',
+    '\u0250': 'a',
+    '\uA733': 'aa',
+    '\u00E6': 'ae',
+    '\u01FD': 'ae',
+    '\u01E3': 'ae',
+    '\uA735': 'ao',
+    '\uA737': 'au',
+    '\uA739': 'av',
+    '\uA73B': 'av',
+    '\uA73D': 'ay',
+    '\u24D1': 'b',
+    '\uFF42': 'b',
+    '\u1E03': 'b',
+    '\u1E05': 'b',
+    '\u1E07': 'b',
+    '\u0180': 'b',
+    '\u0183': 'b',
+    '\u0253': 'b',
+    '\u24D2': 'c',
+    '\uFF43': 'c',
+    '\u0107': 'c',
+    '\u0109': 'c',
+    '\u010B': 'c',
+    '\u010D': 'c',
+    '\u00E7': 'c',
+    '\u1E09': 'c',
+    '\u0188': 'c',
+    '\u023C': 'c',
+    '\uA73F': 'c',
+    '\u2184': 'c',
+    '\u24D3': 'd',
+    '\uFF44': 'd',
+    '\u1E0B': 'd',
+    '\u010F': 'd',
+    '\u1E0D': 'd',
+    '\u1E11': 'd',
+    '\u1E13': 'd',
+    '\u1E0F': 'd',
+    '\u0111': 'd',
+    '\u018C': 'd',
+    '\u0256': 'd',
+    '\u0257': 'd',
+    '\uA77A': 'd',
+    '\u01F3': 'dz',
+    '\u01C6': 'dz',
+    '\u24D4': 'e',
+    '\uFF45': 'e',
+    '\u00E8': 'e',
+    '\u00E9': 'e',
+    '\u00EA': 'e',
+    '\u1EC1': 'e',
+    '\u1EBF': 'e',
+    '\u1EC5': 'e',
+    '\u1EC3': 'e',
+    '\u1EBD': 'e',
+    '\u0113': 'e',
+    '\u1E15': 'e',
+    '\u1E17': 'e',
+    '\u0115': 'e',
+    '\u0117': 'e',
+    '\u00EB': 'e',
+    '\u1EBB': 'e',
+    '\u011B': 'e',
+    '\u0205': 'e',
+    '\u0207': 'e',
+    '\u1EB9': 'e',
+    '\u1EC7': 'e',
+    '\u0229': 'e',
+    '\u1E1D': 'e',
+    '\u0119': 'e',
+    '\u1E19': 'e',
+    '\u1E1B': 'e',
+    '\u0247': 'e',
+    '\u025B': 'e',
+    '\u01DD': 'e',
+    '\u24D5': 'f',
+    '\uFF46': 'f',
+    '\u1E1F': 'f',
+    '\u0192': 'f',
+    '\uA77C': 'f',
+    '\u24D6': 'g',
+    '\uFF47': 'g',
+    '\u01F5': 'g',
+    '\u011D': 'g',
+    '\u1E21': 'g',
+    '\u011F': 'g',
+    '\u0121': 'g',
+    '\u01E7': 'g',
+    '\u0123': 'g',
+    '\u01E5': 'g',
+    '\u0260': 'g',
+    '\uA7A1': 'g',
+    '\u1D79': 'g',
+    '\uA77F': 'g',
+    '\u24D7': 'h',
+    '\uFF48': 'h',
+    '\u0125': 'h',
+    '\u1E23': 'h',
+    '\u1E27': 'h',
+    '\u021F': 'h',
+    '\u1E25': 'h',
+    '\u1E29': 'h',
+    '\u1E2B': 'h',
+    '\u1E96': 'h',
+    '\u0127': 'h',
+    '\u2C68': 'h',
+    '\u2C76': 'h',
+    '\u0265': 'h',
+    '\u0195': 'hv',
+    '\u24D8': 'i',
+    '\uFF49': 'i',
+    '\u00EC': 'i',
+    '\u00ED': 'i',
+    '\u00EE': 'i',
+    '\u0129': 'i',
+    '\u012B': 'i',
+    '\u012D': 'i',
+    '\u00EF': 'i',
+    '\u1E2F': 'i',
+    '\u1EC9': 'i',
+    '\u01D0': 'i',
+    '\u0209': 'i',
+    '\u020B': 'i',
+    '\u1ECB': 'i',
+    '\u012F': 'i',
+    '\u1E2D': 'i',
+    '\u0268': 'i',
+    '\u0131': 'i',
+    '\u24D9': 'j',
+    '\uFF4A': 'j',
+    '\u0135': 'j',
+    '\u01F0': 'j',
+    '\u0249': 'j',
+    '\u24DA': 'k',
+    '\uFF4B': 'k',
+    '\u1E31': 'k',
+    '\u01E9': 'k',
+    '\u1E33': 'k',
+    '\u0137': 'k',
+    '\u1E35': 'k',
+    '\u0199': 'k',
+    '\u2C6A': 'k',
+    '\uA741': 'k',
+    '\uA743': 'k',
+    '\uA745': 'k',
+    '\uA7A3': 'k',
+    '\u24DB': 'l',
+    '\uFF4C': 'l',
+    '\u0140': 'l',
+    '\u013A': 'l',
+    '\u013E': 'l',
+    '\u1E37': 'l',
+    '\u1E39': 'l',
+    '\u013C': 'l',
+    '\u1E3D': 'l',
+    '\u1E3B': 'l',
+    '\u017F': 'l',
+    '\u0142': 'l',
+    '\u019A': 'l',
+    '\u026B': 'l',
+    '\u2C61': 'l',
+    '\uA749': 'l',
+    '\uA781': 'l',
+    '\uA747': 'l',
+    '\u01C9': 'lj',
+    '\u24DC': 'm',
+    '\uFF4D': 'm',
+    '\u1E3F': 'm',
+    '\u1E41': 'm',
+    '\u1E43': 'm',
+    '\u0271': 'm',
+    '\u026F': 'm',
+    '\u24DD': 'n',
+    '\uFF4E': 'n',
+    '\u01F9': 'n',
+    '\u0144': 'n',
+    '\u00F1': 'n',
+    '\u1E45': 'n',
+    '\u0148': 'n',
+    '\u1E47': 'n',
+    '\u0146': 'n',
+    '\u1E4B': 'n',
+    '\u1E49': 'n',
+    '\u019E': 'n',
+    '\u0272': 'n',
+    '\u0149': 'n',
+    '\uA791': 'n',
+    '\uA7A5': 'n',
+    '\u01CC': 'nj',
+    '\u24DE': 'o',
+    '\uFF4F': 'o',
+    '\u00F2': 'o',
+    '\u00F3': 'o',
+    '\u00F4': 'o',
+    '\u1ED3': 'o',
+    '\u1ED1': 'o',
+    '\u1ED7': 'o',
+    '\u1ED5': 'o',
+    '\u00F5': 'o',
+    '\u1E4D': 'o',
+    '\u022D': 'o',
+    '\u1E4F': 'o',
+    '\u014D': 'o',
+    '\u1E51': 'o',
+    '\u1E53': 'o',
+    '\u014F': 'o',
+    '\u022F': 'o',
+    '\u0231': 'o',
+    '\u00F6': 'o',
+    '\u022B': 'o',
+    '\u1ECF': 'o',
+    '\u0151': 'o',
+    '\u01D2': 'o',
+    '\u020D': 'o',
+    '\u020F': 'o',
+    '\u01A1': 'o',
+    '\u1EDD': 'o',
+    '\u1EDB': 'o',
+    '\u1EE1': 'o',
+    '\u1EDF': 'o',
+    '\u1EE3': 'o',
+    '\u1ECD': 'o',
+    '\u1ED9': 'o',
+    '\u01EB': 'o',
+    '\u01ED': 'o',
+    '\u00F8': 'o',
+    '\u01FF': 'o',
+    '\u0254': 'o',
+    '\uA74B': 'o',
+    '\uA74D': 'o',
+    '\u0275': 'o',
+    '\u01A3': 'oi',
+    '\u0223': 'ou',
+    '\uA74F': 'oo',
+    '\u24DF': 'p',
+    '\uFF50': 'p',
+    '\u1E55': 'p',
+    '\u1E57': 'p',
+    '\u01A5': 'p',
+    '\u1D7D': 'p',
+    '\uA751': 'p',
+    '\uA753': 'p',
+    '\uA755': 'p',
+    '\u24E0': 'q',
+    '\uFF51': 'q',
+    '\u024B': 'q',
+    '\uA757': 'q',
+    '\uA759': 'q',
+    '\u24E1': 'r',
+    '\uFF52': 'r',
+    '\u0155': 'r',
+    '\u1E59': 'r',
+    '\u0159': 'r',
+    '\u0211': 'r',
+    '\u0213': 'r',
+    '\u1E5B': 'r',
+    '\u1E5D': 'r',
+    '\u0157': 'r',
+    '\u1E5F': 'r',
+    '\u024D': 'r',
+    '\u027D': 'r',
+    '\uA75B': 'r',
+    '\uA7A7': 'r',
+    '\uA783': 'r',
+    '\u24E2': 's',
+    '\uFF53': 's',
+    '\u00DF': 's',
+    '\u015B': 's',
+    '\u1E65': 's',
+    '\u015D': 's',
+    '\u1E61': 's',
+    '\u0161': 's',
+    '\u1E67': 's',
+    '\u1E63': 's',
+    '\u1E69': 's',
+    '\u0219': 's',
+    '\u015F': 's',
+    '\u023F': 's',
+    '\uA7A9': 's',
+    '\uA785': 's',
+    '\u1E9B': 's',
+    '\u24E3': 't',
+    '\uFF54': 't',
+    '\u1E6B': 't',
+    '\u1E97': 't',
+    '\u0165': 't',
+    '\u1E6D': 't',
+    '\u021B': 't',
+    '\u0163': 't',
+    '\u1E71': 't',
+    '\u1E6F': 't',
+    '\u0167': 't',
+    '\u01AD': 't',
+    '\u0288': 't',
+    '\u2C66': 't',
+    '\uA787': 't',
+    '\uA729': 'tz',
+    '\u24E4': 'u',
+    '\uFF55': 'u',
+    '\u00F9': 'u',
+    '\u00FA': 'u',
+    '\u00FB': 'u',
+    '\u0169': 'u',
+    '\u1E79': 'u',
+    '\u016B': 'u',
+    '\u1E7B': 'u',
+    '\u016D': 'u',
+    '\u00FC': 'u',
+    '\u01DC': 'u',
+    '\u01D8': 'u',
+    '\u01D6': 'u',
+    '\u01DA': 'u',
+    '\u1EE7': 'u',
+    '\u016F': 'u',
+    '\u0171': 'u',
+    '\u01D4': 'u',
+    '\u0215': 'u',
+    '\u0217': 'u',
+    '\u01B0': 'u',
+    '\u1EEB': 'u',
+    '\u1EE9': 'u',
+    '\u1EEF': 'u',
+    '\u1EED': 'u',
+    '\u1EF1': 'u',
+    '\u1EE5': 'u',
+    '\u1E73': 'u',
+    '\u0173': 'u',
+    '\u1E77': 'u',
+    '\u1E75': 'u',
+    '\u0289': 'u',
+    '\u24E5': 'v',
+    '\uFF56': 'v',
+    '\u1E7D': 'v',
+    '\u1E7F': 'v',
+    '\u028B': 'v',
+    '\uA75F': 'v',
+    '\u028C': 'v',
+    '\uA761': 'vy',
+    '\u24E6': 'w',
+    '\uFF57': 'w',
+    '\u1E81': 'w',
+    '\u1E83': 'w',
+    '\u0175': 'w',
+    '\u1E87': 'w',
+    '\u1E85': 'w',
+    '\u1E98': 'w',
+    '\u1E89': 'w',
+    '\u2C73': 'w',
+    '\u24E7': 'x',
+    '\uFF58': 'x',
+    '\u1E8B': 'x',
+    '\u1E8D': 'x',
+    '\u24E8': 'y',
+    '\uFF59': 'y',
+    '\u1EF3': 'y',
+    '\u00FD': 'y',
+    '\u0177': 'y',
+    '\u1EF9': 'y',
+    '\u0233': 'y',
+    '\u1E8F': 'y',
+    '\u00FF': 'y',
+    '\u1EF7': 'y',
+    '\u1E99': 'y',
+    '\u1EF5': 'y',
+    '\u01B4': 'y',
+    '\u024F': 'y',
+    '\u1EFF': 'y',
+    '\u24E9': 'z',
+    '\uFF5A': 'z',
+    '\u017A': 'z',
+    '\u1E91': 'z',
+    '\u017C': 'z',
+    '\u017E': 'z',
+    '\u1E93': 'z',
+    '\u1E95': 'z',
+    '\u01B6': 'z',
+    '\u0225': 'z',
+    '\u0240': 'z',
+    '\u2C6C': 'z',
+    '\uA763': 'z',
+    '\u0386': '\u0391',
+    '\u0388': '\u0395',
+    '\u0389': '\u0397',
+    '\u038A': '\u0399',
+    '\u03AA': '\u0399',
+    '\u038C': '\u039F',
+    '\u038E': '\u03A5',
+    '\u03AB': '\u03A5',
+    '\u038F': '\u03A9',
+    '\u03AC': '\u03B1',
+    '\u03AD': '\u03B5',
+    '\u03AE': '\u03B7',
+    '\u03AF': '\u03B9',
+    '\u03CA': '\u03B9',
+    '\u0390': '\u03B9',
+    '\u03CC': '\u03BF',
+    '\u03CD': '\u03C5',
+    '\u03CB': '\u03C5',
+    '\u03B0': '\u03C5',
+    '\u03C9': '\u03C9',
+    '\u03C2': '\u03C3'
+  };
+
+  return diacritics;
+});
+
+S2.define('select2/data/base',[
+  '../utils'
+], function (Utils) {
+  function BaseAdapter ($element, options) {
+    BaseAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseAdapter, Utils.Observable);
+
+  BaseAdapter.prototype.current = function (callback) {
+    throw new Error('The `current` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.query = function (params, callback) {
+    throw new Error('The `query` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.bind = function (container, $container) {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.destroy = function () {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.generateResultId = function (container, data) {
+    var id = container.id + '-result-';
+
+    id += Utils.generateChars(4);
+
+    if (data.id != null) {
+      id += '-' + data.id.toString();
+    } else {
+      id += '-' + Utils.generateChars(4);
+    }
+    return id;
+  };
+
+  return BaseAdapter;
+});
+
+S2.define('select2/data/select',[
+  './base',
+  '../utils',
+  'jquery'
+], function (BaseAdapter, Utils, $) {
+  function SelectAdapter ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    SelectAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(SelectAdapter, BaseAdapter);
+
+  SelectAdapter.prototype.current = function (callback) {
+    var data = [];
+    var self = this;
+
+    this.$element.find(':selected').each(function () {
+      var $option = $(this);
+
+      var option = self.item($option);
+
+      data.push(option);
+    });
+
+    callback(data);
+  };
+
+  SelectAdapter.prototype.select = function (data) {
+    var self = this;
+
+    data.selected = true;
+
+    // If data.element is a DOM node, use it instead
+    if ($(data.element).is('option')) {
+      data.element.selected = true;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    if (this.$element.prop('multiple')) {
+      this.current(function (currentData) {
+        var val = [];
+
+        data = [data];
+        data.push.apply(data, currentData);
+
+        for (var d = 0; d < data.length; d++) {
+          var id = data[d].id;
+
+          if ($.inArray(id, val) === -1) {
+            val.push(id);
+          }
+        }
+
+        self.$element.val(val);
+        self.$element.trigger('change');
+      });
+    } else {
+      var val = data.id;
+
+      this.$element.val(val);
+      this.$element.trigger('change');
+    }
+  };
+
+  SelectAdapter.prototype.unselect = function (data) {
+    var self = this;
+
+    if (!this.$element.prop('multiple')) {
+      return;
+    }
+
+    data.selected = false;
+
+    if ($(data.element).is('option')) {
+      data.element.selected = false;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    this.current(function (currentData) {
+      var val = [];
+
+      for (var d = 0; d < currentData.length; d++) {
+        var id = currentData[d].id;
+
+        if (id !== data.id && $.inArray(id, val) === -1) {
+          val.push(id);
+        }
+      }
+
+      self.$element.val(val);
+
+      self.$element.trigger('change');
+    });
+  };
+
+  SelectAdapter.prototype.bind = function (container, $container) {
+    var self = this;
+
+    this.container = container;
+
+    container.on('select', function (params) {
+      self.select(params.data);
+    });
+
+    container.on('unselect', function (params) {
+      self.unselect(params.data);
+    });
+  };
+
+  SelectAdapter.prototype.destroy = function () {
+    // Remove anything added to child elements
+    this.$element.find('*').each(function () {
+      // Remove any custom data set by Select2
+      $.removeData(this, 'data');
+    });
+  };
+
+  SelectAdapter.prototype.query = function (params, callback) {
+    var data = [];
+    var self = this;
+
+    var $options = this.$element.children();
+
+    $options.each(function () {
+      var $option = $(this);
+
+      if (!$option.is('option') && !$option.is('optgroup')) {
+        return;
+      }
+
+      var option = self.item($option);
+
+      var matches = self.matches(params, option);
+
+      if (matches !== null) {
+        data.push(matches);
+      }
+    });
+
+    callback({
+      results: data
+    });
+  };
+
+  SelectAdapter.prototype.addOptions = function ($options) {
+    Utils.appendMany(this.$element, $options);
+  };
+
+  SelectAdapter.prototype.option = function (data) {
+    var option;
+
+    if (data.children) {
+      option = document.createElement('optgroup');
+      option.label = data.text;
+    } else {
+      option = document.createElement('option');
+
+      if (option.textContent !== undefined) {
+        option.textContent = data.text;
+      } else {
+        option.innerText = data.text;
+      }
+    }
+
+    if (data.id !== undefined) {
+      option.value = data.id;
+    }
+
+    if (data.disabled) {
+      option.disabled = true;
+    }
+
+    if (data.selected) {
+      option.selected = true;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    var $option = $(option);
+
+    var normalizedData = this._normalizeItem(data);
+    normalizedData.element = option;
+
+    // Override the option's data with the combined data
+    $.data(option, 'data', normalizedData);
+
+    return $option;
+  };
+
+  SelectAdapter.prototype.item = function ($option) {
+    var data = {};
+
+    data = $.data($option[0], 'data');
+
+    if (data != null) {
+      return data;
+    }
+
+    if ($option.is('option')) {
+      data = {
+        id: $option.val(),
+        text: $option.text(),
+        disabled: $option.prop('disabled'),
+        selected: $option.prop('selected'),
+        title: $option.prop('title')
+      };
+    } else if ($option.is('optgroup')) {
+      data = {
+        text: $option.prop('label'),
+        children: [],
+        title: $option.prop('title')
+      };
+
+      var $children = $option.children('option');
+      var children = [];
+
+      for (var c = 0; c < $children.length; c++) {
+        var $child = $($children[c]);
+
+        var child = this.item($child);
+
+        children.push(child);
+      }
+
+      data.children = children;
+    }
+
+    data = this._normalizeItem(data);
+    data.element = $option[0];
+
+    $.data($option[0], 'data', data);
+
+    return data;
+  };
+
+  SelectAdapter.prototype._normalizeItem = function (item) {
+    if (!$.isPlainObject(item)) {
+      item = {
+        id: item,
+        text: item
+      };
+    }
+
+    item = $.extend({}, {
+      text: ''
+    }, item);
+
+    var defaults = {
+      selected: false,
+      disabled: false
+    };
+
+    if (item.id != null) {
+      item.id = item.id.toString();
+    }
+
+    if (item.text != null) {
+      item.text = item.text.toString();
+    }
+
+    if (item._resultId == null && item.id && this.container != null) {
+      item._resultId = this.generateResultId(this.container, item);
+    }
+
+    return $.extend({}, defaults, item);
+  };
+
+  SelectAdapter.prototype.matches = function (params, data) {
+    var matcher = this.options.get('matcher');
+
+    return matcher(params, data);
+  };
+
+  return SelectAdapter;
+});
+
+S2.define('select2/data/array',[
+  './select',
+  '../utils',
+  'jquery'
+], function (SelectAdapter, Utils, $) {
+  function ArrayAdapter ($element, options) {
+    var data = options.get('data') || [];
+
+    ArrayAdapter.__super__.constructor.call(this, $element, options);
+
+    this.addOptions(this.convertToOptions(data));
+  }
+
+  Utils.Extend(ArrayAdapter, SelectAdapter);
+
+  ArrayAdapter.prototype.select = function (data) {
+    var $option = this.$element.find('option').filter(function (i, elm) {
+      return elm.value == data.id.toString();
+    });
+
+    if ($option.length === 0) {
+      $option = this.option(data);
+
+      this.addOptions($option);
+    }
+
+    ArrayAdapter.__super__.select.call(this, data);
+  };
+
+  ArrayAdapter.prototype.convertToOptions = function (data) {
+    var self = this;
+
+    var $existing = this.$element.find('option');
+    var existingIds = $existing.map(function () {
+      return self.item($(this)).id;
+    }).get();
+
+    var $options = [];
+
+    // Filter out all items except for the one passed in the argument
+    function onlyItem (item) {
+      return function () {
+        return $(this).val() == item.id;
+      };
+    }
+
+    for (var d = 0; d < data.length; d++) {
+      var item = this._normalizeItem(data[d]);
+
+      // Skip items which were pre-loaded, only merge the data
+      if ($.inArray(item.id, existingIds) >= 0) {
+        var $existingOption = $existing.filter(onlyItem(item));
+
+        var existingData = this.item($existingOption);
+        var newData = $.extend(true, {}, item, existingData);
+
+        var $newOption = this.option(newData);
+
+        $existingOption.replaceWith($newOption);
+
+        continue;
+      }
+
+      var $option = this.option(item);
+
+      if (item.children) {
+        var $children = this.convertToOptions(item.children);
+
+        Utils.appendMany($option, $children);
+      }
+
+      $options.push($option);
+    }
+
+    return $options;
+  };
+
+  return ArrayAdapter;
+});
+
+S2.define('select2/data/ajax',[
+  './array',
+  '../utils',
+  'jquery'
+], function (ArrayAdapter, Utils, $) {
+  function AjaxAdapter ($element, options) {
+    this.ajaxOptions = this._applyDefaults(options.get('ajax'));
+
+    if (this.ajaxOptions.processResults != null) {
+      this.processResults = this.ajaxOptions.processResults;
+    }
+
+    AjaxAdapter.__super__.constructor.call(this, $element, options);
+  }
+
+  Utils.Extend(AjaxAdapter, ArrayAdapter);
+
+  AjaxAdapter.prototype._applyDefaults = function (options) {
+    var defaults = {
+      data: function (params) {
+        return $.extend({}, params, {
+          q: params.term
+        });
+      },
+      transport: function (params, success, failure) {
+        var $request = $.ajax(params);
+
+        $request.then(success);
+        $request.fail(failure);
+
+        return $request;
+      }
+    };
+
+    return $.extend({}, defaults, options, true);
+  };
+
+  AjaxAdapter.prototype.processResults = function (results) {
+    return results;
+  };
+
+  AjaxAdapter.prototype.query = function (params, callback) {
+    var matches = [];
+    var self = this;
+
+    if (this._request != null) {
+      // JSONP requests cannot always be aborted
+      if ($.isFunction(this._request.abort)) {
+        this._request.abort();
+      }
+
+      this._request = null;
+    }
+
+    var options = $.extend({
+      type: 'GET'
+    }, this.ajaxOptions);
+
+    if (typeof options.url === 'function') {
+      options.url = options.url.call(this.$element, params);
+    }
+
+    if (typeof options.data === 'function') {
+      options.data = options.data.call(this.$element, params);
+    }
+
+    function request () {
+      var $request = options.transport(options, function (data) {
+        var results = self.processResults(data, params);
+
+        if (self.options.get('debug') && window.console && console.error) {
+          // Check to make sure that the response included a `results` key.
+          if (!results || !results.results || !$.isArray(results.results)) {
+            console.error(
+              'Select2: The AJAX results did not return an array in the ' +
+              '`results` key of the response.'
+            );
+          }
+        }
+
+        callback(results);
+      }, function () {
+        // Attempt to detect if a request was aborted
+        // Only works if the transport exposes a status property
+        if ($request.status && $request.status === '0') {
+          return;
+        }
+
+        self.trigger('results:message', {
+          message: 'errorLoading'
+        });
+      });
+
+      self._request = $request;
+    }
+
+    if (this.ajaxOptions.delay && params.term != null) {
+      if (this._queryTimeout) {
+        window.clearTimeout(this._queryTimeout);
+      }
+
+      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
+    } else {
+      request();
+    }
+  };
+
+  return AjaxAdapter;
+});
+
+S2.define('select2/data/tags',[
+  'jquery'
+], function ($) {
+  function Tags (decorated, $element, options) {
+    var tags = options.get('tags');
+
+    var createTag = options.get('createTag');
+
+    if (createTag !== undefined) {
+      this.createTag = createTag;
+    }
+
+    var insertTag = options.get('insertTag');
+
+    if (insertTag !== undefined) {
+        this.insertTag = insertTag;
+    }
+
+    decorated.call(this, $element, options);
+
+    if ($.isArray(tags)) {
+      for (var t = 0; t < tags.length; t++) {
+        var tag = tags[t];
+        var item = this._normalizeItem(tag);
+
+        var $option = this.option(item);
+
+        this.$element.append($option);
+      }
+    }
+  }
+
+  Tags.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    this._removeOldTags();
+
+    if (params.term == null || params.page != null) {
+      decorated.call(this, params, callback);
+      return;
+    }
+
+    function wrapper (obj, child) {
+      var data = obj.results;
+
+      for (var i = 0; i < data.length; i++) {
+        var option = data[i];
+
+        var checkChildren = (
+          option.children != null &&
+          !wrapper({
+            results: option.children
+          }, true)
+        );
+
+        var optionText = (option.text || '').toUpperCase();
+        var paramsTerm = (params.term || '').toUpperCase();
+
+        var checkText = optionText === paramsTerm;
+
+        if (checkText || checkChildren) {
+          if (child) {
+            return false;
+          }
+
+          obj.data = data;
+          callback(obj);
+
+          return;
+        }
+      }
+
+      if (child) {
+        return true;
+      }
+
+      var tag = self.createTag(params);
+
+      if (tag != null) {
+        var $option = self.option(tag);
+        $option.attr('data-select2-tag', true);
+
+        self.addOptions([$option]);
+
+        self.insertTag(data, tag);
+      }
+
+      obj.results = data;
+
+      callback(obj);
+    }
+
+    decorated.call(this, params, wrapper);
+  };
+
+  Tags.prototype.createTag = function (decorated, params) {
+    var term = $.trim(params.term);
+
+    if (term === '') {
+      return null;
+    }
+
+    return {
+      id: term,
+      text: term
+    };
+  };
+
+  Tags.prototype.insertTag = function (_, data, tag) {
+    data.unshift(tag);
+  };
+
+  Tags.prototype._removeOldTags = function (_) {
+    var tag = this._lastTag;
+
+    var $options = this.$element.find('option[data-select2-tag]');
+
+    $options.each(function () {
+      if (this.selected) {
+        return;
+      }
+
+      $(this).remove();
+    });
+  };
+
+  return Tags;
+});
+
+S2.define('select2/data/tokenizer',[
+  'jquery'
+], function ($) {
+  function Tokenizer (decorated, $element, options) {
+    var tokenizer = options.get('tokenizer');
+
+    if (tokenizer !== undefined) {
+      this.tokenizer = tokenizer;
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  Tokenizer.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    this.$search =  container.dropdown.$search || container.selection.$search ||
+      $container.find('.select2-search__field');
+  };
+
+  Tokenizer.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    function createAndSelect (data) {
+      // Normalize the data object so we can use it for checks
+      var item = self._normalizeItem(data);
+
+      // Check if the data object already exists as a tag
+      // Select it if it doesn't
+      var $existingOptions = self.$element.find('option').filter(function () {
+        return $(this).val() === item.id;
+      });
+
+      // If an existing option wasn't found for it, create the option
+      if (!$existingOptions.length) {
+        var $option = self.option(item);
+        $option.attr('data-select2-tag', true);
+
+        self._removeOldTags();
+        self.addOptions([$option]);
+      }
+
+      // Select the item, now that we know there is an option for it
+      select(item);
+    }
+
+    function select (data) {
+      self.trigger('select', {
+        data: data
+      });
+    }
+
+    params.term = params.term || '';
+
+    var tokenData = this.tokenizer(params, this.options, createAndSelect);
+
+    if (tokenData.term !== params.term) {
+      // Replace the search term if we have the search box
+      if (this.$search.length) {
+        this.$search.val(tokenData.term);
+        this.$search.focus();
+      }
+
+      params.term = tokenData.term;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
+    var separators = options.get('tokenSeparators') || [];
+    var term = params.term;
+    var i = 0;
+
+    var createTag = this.createTag || function (params) {
+      return {
+        id: params.term,
+        text: params.term
+      };
+    };
+
+    while (i < term.length) {
+      var termChar = term[i];
+
+      if ($.inArray(termChar, separators) === -1) {
+        i++;
+
+        continue;
+      }
+
+      var part = term.substr(0, i);
+      var partParams = $.extend({}, params, {
+        term: part
+      });
+
+      var data = createTag(partParams);
+
+      if (data == null) {
+        i++;
+        continue;
+      }
+
+      callback(data);
+
+      // Reset the term to not include the tokenized portion
+      term = term.substr(i + 1) || '';
+      i = 0;
+    }
+
+    return {
+      term: term
+    };
+  };
+
+  return Tokenizer;
+});
+
+S2.define('select2/data/minimumInputLength',[
+
+], function () {
+  function MinimumInputLength (decorated, $e, options) {
+    this.minimumInputLength = options.get('minimumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MinimumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (params.term.length < this.minimumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooShort',
+        args: {
+          minimum: this.minimumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MinimumInputLength;
+});
+
+S2.define('select2/data/maximumInputLength',[
+
+], function () {
+  function MaximumInputLength (decorated, $e, options) {
+    this.maximumInputLength = options.get('maximumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (this.maximumInputLength > 0 &&
+        params.term.length > this.maximumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooLong',
+        args: {
+          maximum: this.maximumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MaximumInputLength;
+});
+
+S2.define('select2/data/maximumSelectionLength',[
+
+], function (){
+  function MaximumSelectionLength (decorated, $e, options) {
+    this.maximumSelectionLength = options.get('maximumSelectionLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumSelectionLength.prototype.query =
+    function (decorated, params, callback) {
+      var self = this;
+
+      this.current(function (currentData) {
+        var count = currentData != null ? currentData.length : 0;
+        if (self.maximumSelectionLength > 0 &&
+          count >= self.maximumSelectionLength) {
+          self.trigger('results:message', {
+            message: 'maximumSelected',
+            args: {
+              maximum: self.maximumSelectionLength
+            }
+          });
+          return;
+        }
+        decorated.call(self, params, callback);
+      });
+  };
+
+  return MaximumSelectionLength;
+});
+
+S2.define('select2/dropdown',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Dropdown ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    Dropdown.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Dropdown, Utils.Observable);
+
+  Dropdown.prototype.render = function () {
+    var $dropdown = $(
+      '<span class="select2-dropdown">' +
+        '<span class="select2-results"></span>' +
+      '</span>'
+    );
+
+    $dropdown.attr('dir', this.options.get('dir'));
+
+    this.$dropdown = $dropdown;
+
+    return $dropdown;
+  };
+
+  Dropdown.prototype.bind = function () {
+    // Should be implemented in subclasses
+  };
+
+  Dropdown.prototype.position = function ($dropdown, $container) {
+    // Should be implmented in subclasses
+  };
+
+  Dropdown.prototype.destroy = function () {
+    // Remove the dropdown from the DOM
+    this.$dropdown.remove();
+  };
+
+  return Dropdown;
+});
+
+S2.define('select2/dropdown/search',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function Search () { }
+
+  Search.prototype.render = function (decorated) {
+    var $rendered = decorated.call(this);
+
+    var $search = $(
+      '<span class="select2-search select2-search--dropdown">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" />' +
+      '</span>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    $rendered.prepend($search);
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    this.$search.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+    });
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$search.on('input', function (evt) {
+      // Unbind the duplicated `keyup` event
+      $(this).off('keyup');
+    });
+
+    this.$search.on('keyup input', function (evt) {
+      self.handleSearch(evt);
+    });
+
+    container.on('open', function () {
+      self.$search.attr('tabindex', 0);
+
+      self.$search.focus();
+
+      window.setTimeout(function () {
+        self.$search.focus();
+      }, 0);
+    });
+
+    container.on('close', function () {
+      self.$search.attr('tabindex', -1);
+
+      self.$search.val('');
+    });
+
+    container.on('focus', function () {
+      if (!container.isOpen()) {
+        self.$search.focus();
+      }
+    });
+
+    container.on('results:all', function (params) {
+      if (params.query.term == null || params.query.term === '') {
+        var showSearch = self.showSearch(params);
+
+        if (showSearch) {
+          self.$searchContainer.removeClass('select2-search--hide');
+        } else {
+          self.$searchContainer.addClass('select2-search--hide');
+        }
+      }
+    });
+  };
+
+  Search.prototype.handleSearch = function (evt) {
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.showSearch = function (_, params) {
+    return true;
+  };
+
+  return Search;
+});
+
+S2.define('select2/dropdown/hidePlaceholder',[
+
+], function () {
+  function HidePlaceholder (decorated, $element, options, dataAdapter) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  HidePlaceholder.prototype.append = function (decorated, data) {
+    data.results = this.removePlaceholder(data.results);
+
+    decorated.call(this, data);
+  };
+
+  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  HidePlaceholder.prototype.removePlaceholder = function (_, data) {
+    var modifiedData = data.slice(0);
+
+    for (var d = data.length - 1; d >= 0; d--) {
+      var item = data[d];
+
+      if (this.placeholder.id === item.id) {
+        modifiedData.splice(d, 1);
+      }
+    }
+
+    return modifiedData;
+  };
+
+  return HidePlaceholder;
+});
+
+S2.define('select2/dropdown/infiniteScroll',[
+  'jquery'
+], function ($) {
+  function InfiniteScroll (decorated, $element, options, dataAdapter) {
+    this.lastParams = {};
+
+    decorated.call(this, $element, options, dataAdapter);
+
+    this.$loadingMore = this.createLoadingMore();
+    this.loading = false;
+  }
+
+  InfiniteScroll.prototype.append = function (decorated, data) {
+    this.$loadingMore.remove();
+    this.loading = false;
+
+    decorated.call(this, data);
+
+    if (this.showLoadingMore(data)) {
+      this.$results.append(this.$loadingMore);
+    }
+  };
+
+  InfiniteScroll.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('query', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    container.on('query:append', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    this.$results.on('scroll', function () {
+      var isLoadMoreVisible = $.contains(
+        document.documentElement,
+        self.$loadingMore[0]
+      );
+
+      if (self.loading || !isLoadMoreVisible) {
+        return;
+      }
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var loadingMoreOffset = self.$loadingMore.offset().top +
+        self.$loadingMore.outerHeight(false);
+
+      if (currentOffset + 50 >= loadingMoreOffset) {
+        self.loadMore();
+      }
+    });
+  };
+
+  InfiniteScroll.prototype.loadMore = function () {
+    this.loading = true;
+
+    var params = $.extend({}, {page: 1}, this.lastParams);
+
+    params.page++;
+
+    this.trigger('query:append', params);
+  };
+
+  InfiniteScroll.prototype.showLoadingMore = function (_, data) {
+    return data.pagination && data.pagination.more;
+  };
+
+  InfiniteScroll.prototype.createLoadingMore = function () {
+    var $option = $(
+      '<li ' +
+      'class="select2-results__option select2-results__option--load-more"' +
+      'role="treeitem" aria-disabled="true"></li>'
+    );
+
+    var message = this.options.get('translations').get('loadingMore');
+
+    $option.html(message(this.lastParams));
+
+    return $option;
+  };
+
+  return InfiniteScroll;
+});
+
+S2.define('select2/dropdown/attachBody',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function AttachBody (decorated, $element, options) {
+    this.$dropdownParent = options.get('dropdownParent') || $(document.body);
+
+    decorated.call(this, $element, options);
+  }
+
+  AttachBody.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    var setupResultsEvents = false;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self._showDropdown();
+      self._attachPositioningHandler(container);
+
+      if (!setupResultsEvents) {
+        setupResultsEvents = true;
+
+        container.on('results:all', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+
+        container.on('results:append', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+      }
+    });
+
+    container.on('close', function () {
+      self._hideDropdown();
+      self._detachPositioningHandler(container);
+    });
+
+    this.$dropdownContainer.on('mousedown', function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  AttachBody.prototype.destroy = function (decorated) {
+    decorated.call(this);
+
+    this.$dropdownContainer.remove();
+  };
+
+  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
+    // Clone all of the container classes
+    $dropdown.attr('class', $container.attr('class'));
+
+    $dropdown.removeClass('select2');
+    $dropdown.addClass('select2-container--open');
+
+    $dropdown.css({
+      position: 'absolute',
+      top: -999999
+    });
+
+    this.$container = $container;
+  };
+
+  AttachBody.prototype.render = function (decorated) {
+    var $container = $('<span></span>');
+
+    var $dropdown = decorated.call(this);
+    $container.append($dropdown);
+
+    this.$dropdownContainer = $container;
+
+    return $container;
+  };
+
+  AttachBody.prototype._hideDropdown = function (decorated) {
+    this.$dropdownContainer.detach();
+  };
+
+  AttachBody.prototype._attachPositioningHandler =
+      function (decorated, container) {
+    var self = this;
+
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.each(function () {
+      $(this).data('select2-scroll-position', {
+        x: $(this).scrollLeft(),
+        y: $(this).scrollTop()
+      });
+    });
+
+    $watchers.on(scrollEvent, function (ev) {
+      var position = $(this).data('select2-scroll-position');
+      $(this).scrollTop(position.y);
+    });
+
+    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
+      function (e) {
+      self._positionDropdown();
+      self._resizeDropdown();
+    });
+  };
+
+  AttachBody.prototype._detachPositioningHandler =
+      function (decorated, container) {
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.off(scrollEvent);
+
+    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
+  };
+
+  AttachBody.prototype._positionDropdown = function () {
+    var $window = $(window);
+
+    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
+    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
+
+    var newDirection = null;
+
+    var offset = this.$container.offset();
+
+    offset.bottom = offset.top + this.$container.outerHeight(false);
+
+    var container = {
+      height: this.$container.outerHeight(false)
+    };
+
+    container.top = offset.top;
+    container.bottom = offset.top + container.height;
+
+    var dropdown = {
+      height: this.$dropdown.outerHeight(false)
+    };
+
+    var viewport = {
+      top: $window.scrollTop(),
+      bottom: $window.scrollTop() + $window.height()
+    };
+
+    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
+    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
+
+    var css = {
+      left: offset.left,
+      top: container.bottom
+    };
+
+    // Determine what the parent element is to use for calciulating the offset
+    var $offsetParent = this.$dropdownParent;
+
+    // For statically positoned elements, we need to get the element
+    // that is determining the offset
+    if ($offsetParent.css('position') === 'static') {
+      $offsetParent = $offsetParent.offsetParent();
+    }
+
+    var parentOffset = $offsetParent.offset();
+
+    css.top -= parentOffset.top;
+    css.left -= parentOffset.left;
+
+    if (!isCurrentlyAbove && !isCurrentlyBelow) {
+      newDirection = 'below';
+    }
+
+    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
+      newDirection = 'above';
+    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
+      newDirection = 'below';
+    }
+
+    if (newDirection == 'above' ||
+      (isCurrentlyAbove && newDirection !== 'below')) {
+      css.top = container.top - parentOffset.top - dropdown.height;
+    }
+
+    if (newDirection != null) {
+      this.$dropdown
+        .removeClass('select2-dropdown--below select2-dropdown--above')
+        .addClass('select2-dropdown--' + newDirection);
+      this.$container
+        .removeClass('select2-container--below select2-container--above')
+        .addClass('select2-container--' + newDirection);
+    }
+
+    this.$dropdownContainer.css(css);
+  };
+
+  AttachBody.prototype._resizeDropdown = function () {
+    var css = {
+      width: this.$container.outerWidth(false) + 'px'
+    };
+
+    if (this.options.get('dropdownAutoWidth')) {
+      css.minWidth = css.width;
+      css.position = 'relative';
+      css.width = 'auto';
+    }
+
+    this.$dropdown.css(css);
+  };
+
+  AttachBody.prototype._showDropdown = function (decorated) {
+    this.$dropdownContainer.appendTo(this.$dropdownParent);
+
+    this._positionDropdown();
+    this._resizeDropdown();
+  };
+
+  return AttachBody;
+});
+
+S2.define('select2/dropdown/minimumResultsForSearch',[
+
+], function () {
+  function countResults (data) {
+    var count = 0;
+
+    for (var d = 0; d < data.length; d++) {
+      var item = data[d];
+
+      if (item.children) {
+        count += countResults(item.children);
+      } else {
+        count++;
+      }
+    }
+
+    return count;
+  }
+
+  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
+    this.minimumResultsForSearch = options.get('minimumResultsForSearch');
+
+    if (this.minimumResultsForSearch < 0) {
+      this.minimumResultsForSearch = Infinity;
+    }
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
+    if (countResults(params.data.results) < this.minimumResultsForSearch) {
+      return false;
+    }
+
+    return decorated.call(this, params);
+  };
+
+  return MinimumResultsForSearch;
+});
+
+S2.define('select2/dropdown/selectOnClose',[
+
+], function () {
+  function SelectOnClose () { }
+
+  SelectOnClose.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('close', function (params) {
+      self._handleSelectOnClose(params);
+    });
+  };
+
+  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
+    if (params && params.originalSelect2Event != null) {
+      var event = params.originalSelect2Event;
+
+      // Don't select an item if the close event was triggered from a select or
+      // unselect event
+      if (event._type === 'select' || event._type === 'unselect') {
+        return;
+      }
+    }
+
+    var $highlightedResults = this.getHighlightedResults();
+
+    // Only select highlighted results
+    if ($highlightedResults.length < 1) {
+      return;
+    }
+
+    var data = $highlightedResults.data('data');
+
+    // Don't re-select already selected resulte
+    if (
+      (data.element != null && data.element.selected) ||
+      (data.element == null && data.selected)
+    ) {
+      return;
+    }
+
+    this.trigger('select', {
+        data: data
+    });
+  };
+
+  return SelectOnClose;
+});
+
+S2.define('select2/dropdown/closeOnSelect',[
+
+], function () {
+  function CloseOnSelect () { }
+
+  CloseOnSelect.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('select', function (evt) {
+      self._selectTriggered(evt);
+    });
+
+    container.on('unselect', function (evt) {
+      self._selectTriggered(evt);
+    });
+  };
+
+  CloseOnSelect.prototype._selectTriggered = function (_, evt) {
+    var originalEvent = evt.originalEvent;
+
+    // Don't close if the control key is being held
+    if (originalEvent && originalEvent.ctrlKey) {
+      return;
+    }
+
+    this.trigger('close', {
+      originalEvent: originalEvent,
+      originalSelect2Event: evt
+    });
+  };
+
+  return CloseOnSelect;
+});
+
+S2.define('select2/i18n/en',[],function () {
+  // English
+  return {
+    errorLoading: function () {
+      return 'The results could not be loaded.';
+    },
+    inputTooLong: function (args) {
+      var overChars = args.input.length - args.maximum;
+
+      var message = 'Please delete ' + overChars + ' character';
+
+      if (overChars != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    inputTooShort: function (args) {
+      var remainingChars = args.minimum - args.input.length;
+
+      var message = 'Please enter ' + remainingChars + ' or more characters';
+
+      return message;
+    },
+    loadingMore: function () {
+      return 'Loading more results…';
+    },
+    maximumSelected: function (args) {
+      var message = 'You can only select ' + args.maximum + ' item';
+
+      if (args.maximum != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    noResults: function () {
+      return 'No results found';
+    },
+    searching: function () {
+      return 'Searching…';
+    }
+  };
+});
+
+S2.define('select2/defaults',[
+  'jquery',
+  'require',
+
+  './results',
+
+  './selection/single',
+  './selection/multiple',
+  './selection/placeholder',
+  './selection/allowClear',
+  './selection/search',
+  './selection/eventRelay',
+
+  './utils',
+  './translation',
+  './diacritics',
+
+  './data/select',
+  './data/array',
+  './data/ajax',
+  './data/tags',
+  './data/tokenizer',
+  './data/minimumInputLength',
+  './data/maximumInputLength',
+  './data/maximumSelectionLength',
+
+  './dropdown',
+  './dropdown/search',
+  './dropdown/hidePlaceholder',
+  './dropdown/infiniteScroll',
+  './dropdown/attachBody',
+  './dropdown/minimumResultsForSearch',
+  './dropdown/selectOnClose',
+  './dropdown/closeOnSelect',
+
+  './i18n/en'
+], function ($, require,
+
+             ResultsList,
+
+             SingleSelection, MultipleSelection, Placeholder, AllowClear,
+             SelectionSearch, EventRelay,
+
+             Utils, Translation, DIACRITICS,
+
+             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
+             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
+
+             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
+             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
+
+             EnglishTranslation) {
+  function Defaults () {
+    this.reset();
+  }
+
+  Defaults.prototype.apply = function (options) {
+    options = $.extend(true, {}, this.defaults, options);
+
+    if (options.dataAdapter == null) {
+      if (options.ajax != null) {
+        options.dataAdapter = AjaxData;
+      } else if (options.data != null) {
+        options.dataAdapter = ArrayData;
+      } else {
+        options.dataAdapter = SelectData;
+      }
+
+      if (options.minimumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MinimumInputLength
+        );
+      }
+
+      if (options.maximumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumInputLength
+        );
+      }
+
+      if (options.maximumSelectionLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumSelectionLength
+        );
+      }
+
+      if (options.tags) {
+        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
+      }
+
+      if (options.tokenSeparators != null || options.tokenizer != null) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Tokenizer
+        );
+      }
+
+      if (options.query != null) {
+        var Query = require(options.amdBase + 'compat/query');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Query
+        );
+      }
+
+      if (options.initSelection != null) {
+        var InitSelection = require(options.amdBase + 'compat/initSelection');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          InitSelection
+        );
+      }
+    }
+
+    if (options.resultsAdapter == null) {
+      options.resultsAdapter = ResultsList;
+
+      if (options.ajax != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          InfiniteScroll
+        );
+      }
+
+      if (options.placeholder != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          HidePlaceholder
+        );
+      }
+
+      if (options.selectOnClose) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          SelectOnClose
+        );
+      }
+    }
+
+    if (options.dropdownAdapter == null) {
+      if (options.multiple) {
+        options.dropdownAdapter = Dropdown;
+      } else {
+        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
+
+        options.dropdownAdapter = SearchableDropdown;
+      }
+
+      if (options.minimumResultsForSearch !== 0) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          MinimumResultsForSearch
+        );
+      }
+
+      if (options.closeOnSelect) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          CloseOnSelect
+        );
+      }
+
+      if (
+        options.dropdownCssClass != null ||
+        options.dropdownCss != null ||
+        options.adaptDropdownCssClass != null
+      ) {
+        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
+
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          DropdownCSS
+        );
+      }
+
+      options.dropdownAdapter = Utils.Decorate(
+        options.dropdownAdapter,
+        AttachBody
+      );
+    }
+
+    if (options.selectionAdapter == null) {
+      if (options.multiple) {
+        options.selectionAdapter = MultipleSelection;
+      } else {
+        options.selectionAdapter = SingleSelection;
+      }
+
+      // Add the placeholder mixin if a placeholder was specified
+      if (options.placeholder != null) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          Placeholder
+        );
+      }
+
+      if (options.allowClear) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          AllowClear
+        );
+      }
+
+      if (options.multiple) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          SelectionSearch
+        );
+      }
+
+      if (
+        options.containerCssClass != null ||
+        options.containerCss != null ||
+        options.adaptContainerCssClass != null
+      ) {
+        var ContainerCSS = require(options.amdBase + 'compat/containerCss');
+
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          ContainerCSS
+        );
+      }
+
+      options.selectionAdapter = Utils.Decorate(
+        options.selectionAdapter,
+        EventRelay
+      );
+    }
+
+    if (typeof options.language === 'string') {
+      // Check if the language is specified with a region
+      if (options.language.indexOf('-') > 0) {
+        // Extract the region information if it is included
+        var languageParts = options.language.split('-');
+        var baseLanguage = languageParts[0];
+
+        options.language = [options.language, baseLanguage];
+      } else {
+        options.language = [options.language];
+      }
+    }
+
+    if ($.isArray(options.language)) {
+      var languages = new Translation();
+      options.language.push('en');
+
+      var languageNames = options.language;
+
+      for (var l = 0; l < languageNames.length; l++) {
+        var name = languageNames[l];
+        var language = {};
+
+        try {
+          // Try to load it with the original name
+          language = Translation.loadPath(name);
+        } catch (e) {
+          try {
+            // If we couldn't load it, check if it wasn't the full path
+            name = this.defaults.amdLanguageBase + name;
+            language = Translation.loadPath(name);
+          } catch (ex) {
+            // The translation could not be loaded at all. Sometimes this is
+            // because of a configuration problem, other times this can be
+            // because of how Select2 helps load all possible translation files.
+            if (options.debug && window.console && console.warn) {
+              console.warn(
+                'Select2: The language file for "' + name + '" could not be ' +
+                'automatically loaded. A fallback will be used instead.'
+              );
+            }
+
+            continue;
+          }
+        }
+
+        languages.extend(language);
+      }
+
+      options.translations = languages;
+    } else {
+      var baseTranslation = Translation.loadPath(
+        this.defaults.amdLanguageBase + 'en'
+      );
+      var customTranslation = new Translation(options.language);
+
+      customTranslation.extend(baseTranslation);
+
+      options.translations = customTranslation;
+    }
+
+    return options;
+  };
+
+  Defaults.prototype.reset = function () {
+    function stripDiacritics (text) {
+      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
+      function match(a) {
+        return DIACRITICS[a] || a;
+      }
+
+      return text.replace(/[^\u0000-\u007E]/g, match);
+    }
+
+    function matcher (params, data) {
+      // Always return the object if there is nothing to compare
+      if ($.trim(params.term) === '') {
+        return data;
+      }
+
+      // Do a recursive check for options with children
+      if (data.children && data.children.length > 0) {
+        // Clone the data object if there are children
+        // This is required as we modify the object to remove any non-matches
+        var match = $.extend(true, {}, data);
+
+        // Check each child of the option
+        for (var c = data.children.length - 1; c >= 0; c--) {
+          var child = data.children[c];
+
+          var matches = matcher(params, child);
+
+          // If there wasn't a match, remove the object in the array
+          if (matches == null) {
+            match.children.splice(c, 1);
+          }
+        }
+
+        // If any children matched, return the new object
+        if (match.children.length > 0) {
+          return match;
+        }
+
+        // If there were no matching children, check just the plain object
+        return matcher(params, match);
+      }
+
+      var original = stripDiacritics(data.text).toUpperCase();
+      var term = stripDiacritics(params.term).toUpperCase();
+
+      // Check if the text contains the term
+      if (original.indexOf(term) > -1) {
+        return data;
+      }
+
+      // If it doesn't contain the term, don't return anything
+      return null;
+    }
+
+    this.defaults = {
+      amdBase: './',
+      amdLanguageBase: './i18n/',
+      closeOnSelect: true,
+      debug: false,
+      dropdownAutoWidth: false,
+      escapeMarkup: Utils.escapeMarkup,
+      language: EnglishTranslation,
+      matcher: matcher,
+      minimumInputLength: 0,
+      maximumInputLength: 0,
+      maximumSelectionLength: 0,
+      minimumResultsForSearch: 0,
+      selectOnClose: false,
+      sorter: function (data) {
+        return data;
+      },
+      templateResult: function (result) {
+        return result.text;
+      },
+      templateSelection: function (selection) {
+        return selection.text;
+      },
+      theme: 'default',
+      width: 'resolve'
+    };
+  };
+
+  Defaults.prototype.set = function (key, value) {
+    var camelKey = $.camelCase(key);
+
+    var data = {};
+    data[camelKey] = value;
+
+    var convertedData = Utils._convertData(data);
+
+    $.extend(this.defaults, convertedData);
+  };
+
+  var defaults = new Defaults();
+
+  return defaults;
+});
+
+S2.define('select2/options',[
+  'require',
+  'jquery',
+  './defaults',
+  './utils'
+], function (require, $, Defaults, Utils) {
+  function Options (options, $element) {
+    this.options = options;
+
+    if ($element != null) {
+      this.fromElement($element);
+    }
+
+    this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
+  }
+
+  Options.prototype.fromElement = function ($e) {
+    var excludedData = ['select2'];
+
+    if (this.options.multiple == null) {
+      this.options.multiple = $e.prop('multiple');
+    }
+
+    if (this.options.disabled == null) {
+      this.options.disabled = $e.prop('disabled');
+    }
+
+    if (this.options.language == null) {
+      if ($e.prop('lang')) {
+        this.options.language = $e.prop('lang').toLowerCase();
+      } else if ($e.closest('[lang]').prop('lang')) {
+        this.options.language = $e.closest('[lang]').prop('lang');
+      }
+    }
+
+    if (this.options.dir == null) {
+      if ($e.prop('dir')) {
+        this.options.dir = $e.prop('dir');
+      } else if ($e.closest('[dir]').prop('dir')) {
+        this.options.dir = $e.closest('[dir]').prop('dir');
+      } else {
+        this.options.dir = 'ltr';
+      }
+    }
+
+    $e.prop('disabled', this.options.disabled);
+    $e.prop('multiple', this.options.multiple);
+
+    if ($e.data('select2Tags')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-select2-tags` attribute has been changed to ' +
+          'use the `data-data` and `data-tags="true"` attributes and will be ' +
+          'removed in future versions of Select2.'
+        );
+      }
+
+      $e.data('data', $e.data('select2Tags'));
+      $e.data('tags', true);
+    }
+
+    if ($e.data('ajaxUrl')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-ajax-url` attribute has been changed to ' +
+          '`data-ajax--url` and support for the old attribute will be removed' +
+          ' in future versions of Select2.'
+        );
+      }
+
+      $e.attr('ajax--url', $e.data('ajaxUrl'));
+      $e.data('ajax--url', $e.data('ajaxUrl'));
+    }
+
+    var dataset = {};
+
+    // Prefer the element's `dataset` attribute if it exists
+    // jQuery 1.x does not correctly handle data attributes with multiple dashes
+    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
+      dataset = $.extend(true, {}, $e[0].dataset, $e.data());
+    } else {
+      dataset = $e.data();
+    }
+
+    var data = $.extend(true, {}, dataset);
+
+    data = Utils._convertData(data);
+
+    for (var key in data) {
+      if ($.inArray(key, excludedData) > -1) {
+        continue;
+      }
+
+      if ($.isPlainObject(this.options[key])) {
+        $.extend(this.options[key], data[key]);
+      } else {
+        this.options[key] = data[key];
+      }
+    }
+
+    return this;
+  };
+
+  Options.prototype.get = function (key) {
+    return this.options[key];
+  };
+
+  Options.prototype.set = function (key, val) {
+    this.options[key] = val;
+  };
+
+  return Options;
+});
+
+S2.define('select2/core',[
+  'jquery',
+  './options',
+  './utils',
+  './keys'
+], function ($, Options, Utils, KEYS) {
+  var Select2 = function ($element, options) {
+    if ($element.data('select2') != null) {
+      $element.data('select2').destroy();
+    }
+
+    this.$element = $element;
+
+    this.id = this._generateId($element);
+
+    options = options || {};
+
+    this.options = new Options(options, $element);
+
+    Select2.__super__.constructor.call(this);
+
+    // Set up the tabindex
+
+    var tabindex = $element.attr('tabindex') || 0;
+    $element.data('old-tabindex', tabindex);
+    $element.attr('tabindex', '-1');
+
+    // Set up containers and adapters
+
+    var DataAdapter = this.options.get('dataAdapter');
+    this.dataAdapter = new DataAdapter($element, this.options);
+
+    var $container = this.render();
+
+    this._placeContainer($container);
+
+    var SelectionAdapter = this.options.get('selectionAdapter');
+    this.selection = new SelectionAdapter($element, this.options);
+    this.$selection = this.selection.render();
+
+    this.selection.position(this.$selection, $container);
+
+    var DropdownAdapter = this.options.get('dropdownAdapter');
+    this.dropdown = new DropdownAdapter($element, this.options);
+    this.$dropdown = this.dropdown.render();
+
+    this.dropdown.position(this.$dropdown, $container);
+
+    var ResultsAdapter = this.options.get('resultsAdapter');
+    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
+    this.$results = this.results.render();
+
+    this.results.position(this.$results, this.$dropdown);
+
+    // Bind events
+
+    var self = this;
+
+    // Bind the container to all of the adapters
+    this._bindAdapters();
+
+    // Register any DOM event handlers
+    this._registerDomEvents();
+
+    // Register any internal event handlers
+    this._registerDataEvents();
+    this._registerSelectionEvents();
+    this._registerDropdownEvents();
+    this._registerResultsEvents();
+    this._registerEvents();
+
+    // Set the initial state
+    this.dataAdapter.current(function (initialData) {
+      self.trigger('selection:update', {
+        data: initialData
+      });
+    });
+
+    // Hide the original select
+    $element.addClass('select2-hidden-accessible');
+    $element.attr('aria-hidden', 'true');
+
+    // Synchronize any monitored attributes
+    this._syncAttributes();
+
+    $element.data('select2', this);
+  };
+
+  Utils.Extend(Select2, Utils.Observable);
+
+  Select2.prototype._generateId = function ($element) {
+    var id = '';
+
+    if ($element.attr('id') != null) {
+      id = $element.attr('id');
+    } else if ($element.attr('name') != null) {
+      id = $element.attr('name') + '-' + Utils.generateChars(2);
+    } else {
+      id = Utils.generateChars(4);
+    }
+
+    id = id.replace(/(:|\.|\[|\]|,)/g, '');
+    id = 'select2-' + id;
+
+    return id;
+  };
+
+  Select2.prototype._placeContainer = function ($container) {
+    $container.insertAfter(this.$element);
+
+    var width = this._resolveWidth(this.$element, this.options.get('width'));
+
+    if (width != null) {
+      $container.css('width', width);
+    }
+  };
+
+  Select2.prototype._resolveWidth = function ($element, method) {
+    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
+
+    if (method == 'resolve') {
+      var styleWidth = this._resolveWidth($element, 'style');
+
+      if (styleWidth != null) {
+        return styleWidth;
+      }
+
+      return this._resolveWidth($element, 'element');
+    }
+
+    if (method == 'element') {
+      var elementWidth = $element.outerWidth(false);
+
+      if (elementWidth <= 0) {
+        return 'auto';
+      }
+
+      return elementWidth + 'px';
+    }
+
+    if (method == 'style') {
+      var style = $element.attr('style');
+
+      if (typeof(style) !== 'string') {
+        return null;
+      }
+
+      var attrs = style.split(';');
+
+      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
+        var attr = attrs[i].replace(/\s/g, '');
+        var matches = attr.match(WIDTH);
+
+        if (matches !== null && matches.length >= 1) {
+          return matches[1];
+        }
+      }
+
+      return null;
+    }
+
+    return method;
+  };
+
+  Select2.prototype._bindAdapters = function () {
+    this.dataAdapter.bind(this, this.$container);
+    this.selection.bind(this, this.$container);
+
+    this.dropdown.bind(this, this.$container);
+    this.results.bind(this, this.$container);
+  };
+
+  Select2.prototype._registerDomEvents = function () {
+    var self = this;
+
+    this.$element.on('change.select2', function () {
+      self.dataAdapter.current(function (data) {
+        self.trigger('selection:update', {
+          data: data
+        });
+      });
+    });
+
+    this.$element.on('focus.select2', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this._syncA = Utils.bind(this._syncAttributes, this);
+    this._syncS = Utils.bind(this._syncSubtree, this);
+
+    if (this.$element[0].attachEvent) {
+      this.$element[0].attachEvent('onpropertychange', this._syncA);
+    }
+
+    var observer = window.MutationObserver ||
+      window.WebKitMutationObserver ||
+      window.MozMutationObserver
+    ;
+
+    if (observer != null) {
+      this._observer = new observer(function (mutations) {
+        $.each(mutations, self._syncA);
+        $.each(mutations, self._syncS);
+      });
+      this._observer.observe(this.$element[0], {
+        attributes: true,
+        childList: true,
+        subtree: false
+      });
+    } else if (this.$element[0].addEventListener) {
+      this.$element[0].addEventListener(
+        'DOMAttrModified',
+        self._syncA,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeInserted',
+        self._syncS,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeRemoved',
+        self._syncS,
+        false
+      );
+    }
+  };
+
+  Select2.prototype._registerDataEvents = function () {
+    var self = this;
+
+    this.dataAdapter.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerSelectionEvents = function () {
+    var self = this;
+    var nonRelayEvents = ['toggle', 'focus'];
+
+    this.selection.on('toggle', function () {
+      self.toggleDropdown();
+    });
+
+    this.selection.on('focus', function (params) {
+      self.focus(params);
+    });
+
+    this.selection.on('*', function (name, params) {
+      if ($.inArray(name, nonRelayEvents) !== -1) {
+        return;
+      }
+
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerDropdownEvents = function () {
+    var self = this;
+
+    this.dropdown.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerResultsEvents = function () {
+    var self = this;
+
+    this.results.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerEvents = function () {
+    var self = this;
+
+    this.on('open', function () {
+      self.$container.addClass('select2-container--open');
+    });
+
+    this.on('close', function () {
+      self.$container.removeClass('select2-container--open');
+    });
+
+    this.on('enable', function () {
+      self.$container.removeClass('select2-container--disabled');
+    });
+
+    this.on('disable', function () {
+      self.$container.addClass('select2-container--disabled');
+    });
+
+    this.on('blur', function () {
+      self.$container.removeClass('select2-container--focus');
+    });
+
+    this.on('query', function (params) {
+      if (!self.isOpen()) {
+        self.trigger('open', {});
+      }
+
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:all', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('query:append', function (params) {
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:append', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('keypress', function (evt) {
+      var key = evt.which;
+
+      if (self.isOpen()) {
+        if (key === KEYS.ESC || key === KEYS.TAB ||
+            (key === KEYS.UP && evt.altKey)) {
+          self.close();
+
+          evt.preventDefault();
+        } else if (key === KEYS.ENTER) {
+          self.trigger('results:select', {});
+
+          evt.preventDefault();
+        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
+          self.trigger('results:toggle', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.UP) {
+          self.trigger('results:previous', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.DOWN) {
+          self.trigger('results:next', {});
+
+          evt.preventDefault();
+        }
+      } else {
+        if (key === KEYS.ENTER || key === KEYS.SPACE ||
+            (key === KEYS.DOWN && evt.altKey)) {
+          self.open();
+
+          evt.preventDefault();
+        }
+      }
+    });
+  };
+
+  Select2.prototype._syncAttributes = function () {
+    this.options.set('disabled', this.$element.prop('disabled'));
+
+    if (this.options.get('disabled')) {
+      if (this.isOpen()) {
+        this.close();
+      }
+
+      this.trigger('disable', {});
+    } else {
+      this.trigger('enable', {});
+    }
+  };
+
+  Select2.prototype._syncSubtree = function (evt, mutations) {
+    var changed = false;
+    var self = this;
+
+    // Ignore any mutation events raised for elements that aren't options or
+    // optgroups. This handles the case when the select element is destroyed
+    if (
+      evt && evt.target && (
+        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
+      )
+    ) {
+      return;
+    }
+
+    if (!mutations) {
+      // If mutation events aren't supported, then we can only assume that the
+      // change affected the selections
+      changed = true;
+    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
+      for (var n = 0; n < mutations.addedNodes.length; n++) {
+        var node = mutations.addedNodes[n];
+
+        if (node.selected) {
+          changed = true;
+        }
+      }
+    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
+      changed = true;
+    }
+
+    // Only re-pull the data if we think there is a change
+    if (changed) {
+      this.dataAdapter.current(function (currentData) {
+        self.trigger('selection:update', {
+          data: currentData
+        });
+      });
+    }
+  };
+
+  /**
+   * Override the trigger method to automatically trigger pre-events when
+   * there are events that can be prevented.
+   */
+  Select2.prototype.trigger = function (name, args) {
+    var actualTrigger = Select2.__super__.trigger;
+    var preTriggerMap = {
+      'open': 'opening',
+      'close': 'closing',
+      'select': 'selecting',
+      'unselect': 'unselecting'
+    };
+
+    if (args === undefined) {
+      args = {};
+    }
+
+    if (name in preTriggerMap) {
+      var preTriggerName = preTriggerMap[name];
+      var preTriggerArgs = {
+        prevented: false,
+        name: name,
+        args: args
+      };
+
+      actualTrigger.call(this, preTriggerName, preTriggerArgs);
+
+      if (preTriggerArgs.prevented) {
+        args.prevented = true;
+
+        return;
+      }
+    }
+
+    actualTrigger.call(this, name, args);
+  };
+
+  Select2.prototype.toggleDropdown = function () {
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    if (this.isOpen()) {
+      this.close();
+    } else {
+      this.open();
+    }
+  };
+
+  Select2.prototype.open = function () {
+    if (this.isOpen()) {
+      return;
+    }
+
+    this.trigger('query', {});
+  };
+
+  Select2.prototype.close = function () {
+    if (!this.isOpen()) {
+      return;
+    }
+
+    this.trigger('close', {});
+  };
+
+  Select2.prototype.isOpen = function () {
+    return this.$container.hasClass('select2-container--open');
+  };
+
+  Select2.prototype.hasFocus = function () {
+    return this.$container.hasClass('select2-container--focus');
+  };
+
+  Select2.prototype.focus = function (data) {
+    // No need to re-trigger focus events if we are already focused
+    if (this.hasFocus()) {
+      return;
+    }
+
+    this.$container.addClass('select2-container--focus');
+    this.trigger('focus', {});
+  };
+
+  Select2.prototype.enable = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("enable")` method has been deprecated and will' +
+        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
+        ' instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      args = [true];
+    }
+
+    var disabled = !args[0];
+
+    this.$element.prop('disabled', disabled);
+  };
+
+  Select2.prototype.data = function () {
+    if (this.options.get('debug') &&
+        arguments.length > 0 && window.console && console.warn) {
+      console.warn(
+        'Select2: Data can no longer be set using `select2("data")`. You ' +
+        'should consider setting the value instead using `$element.val()`.'
+      );
+    }
+
+    var data = [];
+
+    this.dataAdapter.current(function (currentData) {
+      data = currentData;
+    });
+
+    return data;
+  };
+
+  Select2.prototype.val = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("val")` method has been deprecated and will be' +
+        ' removed in later Select2 versions. Use $element.val() instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      return this.$element.val();
+    }
+
+    var newVal = args[0];
+
+    if ($.isArray(newVal)) {
+      newVal = $.map(newVal, function (obj) {
+        return obj.toString();
+      });
+    }
+
+    this.$element.val(newVal).trigger('change');
+  };
+
+  Select2.prototype.destroy = function () {
+    this.$container.remove();
+
+    if (this.$element[0].detachEvent) {
+      this.$element[0].detachEvent('onpropertychange', this._syncA);
+    }
+
+    if (this._observer != null) {
+      this._observer.disconnect();
+      this._observer = null;
+    } else if (this.$element[0].removeEventListener) {
+      this.$element[0]
+        .removeEventListener('DOMAttrModified', this._syncA, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeInserted', this._syncS, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeRemoved', this._syncS, false);
+    }
+
+    this._syncA = null;
+    this._syncS = null;
+
+    this.$element.off('.select2');
+    this.$element.attr('tabindex', this.$element.data('old-tabindex'));
+
+    this.$element.removeClass('select2-hidden-accessible');
+    this.$element.attr('aria-hidden', 'false');
+    this.$element.removeData('select2');
+
+    this.dataAdapter.destroy();
+    this.selection.destroy();
+    this.dropdown.destroy();
+    this.results.destroy();
+
+    this.dataAdapter = null;
+    this.selection = null;
+    this.dropdown = null;
+    this.results = null;
+  };
+
+  Select2.prototype.render = function () {
+    var $container = $(
+      '<span class="select2 select2-container">' +
+        '<span class="selection"></span>' +
+        '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
+      '</span>'
+    );
+
+    $container.attr('dir', this.options.get('dir'));
+
+    this.$container = $container;
+
+    this.$container.addClass('select2-container--' + this.options.get('theme'));
+
+    $container.data('element', this.$element);
+
+    return $container;
+  };
+
+  return Select2;
+});
+
+S2.define('select2/compat/utils',[
+  'jquery'
+], function ($) {
+  function syncCssClasses ($dest, $src, adapter) {
+    var classes, replacements = [], adapted;
+
+    classes = $.trim($dest.attr('class'));
+
+    if (classes) {
+      classes = '' + classes; // for IE which returns object
+
+      $(classes.split(/\s+/)).each(function () {
+        // Save all Select2 classes
+        if (this.indexOf('select2-') === 0) {
+          replacements.push(this);
+        }
+      });
+    }
+
+    classes = $.trim($src.attr('class'));
+
+    if (classes) {
+      classes = '' + classes; // for IE which returns object
+
+      $(classes.split(/\s+/)).each(function () {
+        // Only adapt non-Select2 classes
+        if (this.indexOf('select2-') !== 0) {
+          adapted = adapter(this);
+
+          if (adapted != null) {
+            replacements.push(adapted);
+          }
+        }
+      });
+    }
+
+    $dest.attr('class', replacements.join(' '));
+  }
+
+  return {
+    syncCssClasses: syncCssClasses
+  };
+});
+
+S2.define('select2/compat/containerCss',[
+  'jquery',
+  './utils'
+], function ($, CompatUtils) {
+  // No-op CSS adapter that discards all classes by default
+  function _containerAdapter (clazz) {
+    return null;
+  }
+
+  function ContainerCSS () { }
+
+  ContainerCSS.prototype.render = function (decorated) {
+    var $container = decorated.call(this);
+
+    var containerCssClass = this.options.get('containerCssClass') || '';
+
+    if ($.isFunction(containerCssClass)) {
+      containerCssClass = containerCssClass(this.$element);
+    }
+
+    var containerCssAdapter = this.options.get('adaptContainerCssClass');
+    containerCssAdapter = containerCssAdapter || _containerAdapter;
+
+    if (containerCssClass.indexOf(':all:') !== -1) {
+      containerCssClass = containerCssClass.replace(':all:', '');
+
+      var _cssAdapter = containerCssAdapter;
+
+      containerCssAdapter = function (clazz) {
+        var adapted = _cssAdapter(clazz);
+
+        if (adapted != null) {
+          // Append the old one along with the adapted one
+          return adapted + ' ' + clazz;
+        }
+
+        return clazz;
+      };
+    }
+
+    var containerCss = this.options.get('containerCss') || {};
+
+    if ($.isFunction(containerCss)) {
+      containerCss = containerCss(this.$element);
+    }
+
+    CompatUtils.syncCssClasses($container, this.$element, containerCssAdapter);
+
+    $container.css(containerCss);
+    $container.addClass(containerCssClass);
+
+    return $container;
+  };
+
+  return ContainerCSS;
+});
+
+S2.define('select2/compat/dropdownCss',[
+  'jquery',
+  './utils'
+], function ($, CompatUtils) {
+  // No-op CSS adapter that discards all classes by default
+  function _dropdownAdapter (clazz) {
+    return null;
+  }
+
+  function DropdownCSS () { }
+
+  DropdownCSS.prototype.render = function (decorated) {
+    var $dropdown = decorated.call(this);
+
+    var dropdownCssClass = this.options.get('dropdownCssClass') || '';
+
+    if ($.isFunction(dropdownCssClass)) {
+      dropdownCssClass = dropdownCssClass(this.$element);
+    }
+
+    var dropdownCssAdapter = this.options.get('adaptDropdownCssClass');
+    dropdownCssAdapter = dropdownCssAdapter || _dropdownAdapter;
+
+    if (dropdownCssClass.indexOf(':all:') !== -1) {
+      dropdownCssClass = dropdownCssClass.replace(':all:', '');
+
+      var _cssAdapter = dropdownCssAdapter;
+
+      dropdownCssAdapter = function (clazz) {
+        var adapted = _cssAdapter(clazz);
+
+        if (adapted != null) {
+          // Append the old one along with the adapted one
+          return adapted + ' ' + clazz;
+        }
+
+        return clazz;
+      };
+    }
+
+    var dropdownCss = this.options.get('dropdownCss') || {};
+
+    if ($.isFunction(dropdownCss)) {
+      dropdownCss = dropdownCss(this.$element);
+    }
+
+    CompatUtils.syncCssClasses($dropdown, this.$element, dropdownCssAdapter);
+
+    $dropdown.css(dropdownCss);
+    $dropdown.addClass(dropdownCssClass);
+
+    return $dropdown;
+  };
+
+  return DropdownCSS;
+});
+
+S2.define('select2/compat/initSelection',[
+  'jquery'
+], function ($) {
+  function InitSelection (decorated, $element, options) {
+    if (options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `initSelection` option has been deprecated in favor' +
+        ' of a custom data adapter that overrides the `current` method. ' +
+        'This method is now called multiple times instead of a single ' +
+        'time when the instance is initialized. Support will be removed ' +
+        'for the `initSelection` option in future versions of Select2'
+      );
+    }
+
+    this.initSelection = options.get('initSelection');
+    this._isInitialized = false;
+
+    decorated.call(this, $element, options);
+  }
+
+  InitSelection.prototype.current = function (decorated, callback) {
+    var self = this;
+
+    if (this._isInitialized) {
+      decorated.call(this, callback);
+
+      return;
+    }
+
+    this.initSelection.call(null, this.$element, function (data) {
+      self._isInitialized = true;
+
+      if (!$.isArray(data)) {
+        data = [data];
+      }
+
+      callback(data);
+    });
+  };
+
+  return InitSelection;
+});
+
+S2.define('select2/compat/inputData',[
+  'jquery'
+], function ($) {
+  function InputData (decorated, $element, options) {
+    this._currentData = [];
+    this._valueSeparator = options.get('valueSeparator') || ',';
+
+    if ($element.prop('type') === 'hidden') {
+      if (options.get('debug') && console && console.warn) {
+        console.warn(
+          'Select2: Using a hidden input with Select2 is no longer ' +
+          'supported and may stop working in the future. It is recommended ' +
+          'to use a `<select>` element instead.'
+        );
+      }
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  InputData.prototype.current = function (_, callback) {
+    function getSelected (data, selectedIds) {
+      var selected = [];
+
+      if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
+        data.selected = true;
+        selected.push(data);
+      } else {
+        data.selected = false;
+      }
+
+      if (data.children) {
+        selected.push.apply(selected, getSelected(data.children, selectedIds));
+      }
+
+      return selected;
+    }
+
+    var selected = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      selected.push.apply(
+        selected,
+        getSelected(
+          data,
+          this.$element.val().split(
+            this._valueSeparator
+          )
+        )
+      );
+    }
+
+    callback(selected);
+  };
+
+  InputData.prototype.select = function (_, data) {
+    if (!this.options.get('multiple')) {
+      this.current(function (allData) {
+        $.map(allData, function (data) {
+          data.selected = false;
+        });
+      });
+
+      this.$element.val(data.id);
+      this.$element.trigger('change');
+    } else {
+      var value = this.$element.val();
+      value += this._valueSeparator + data.id;
+
+      this.$element.val(value);
+      this.$element.trigger('change');
+    }
+  };
+
+  InputData.prototype.unselect = function (_, data) {
+    var self = this;
+
+    data.selected = false;
+
+    this.current(function (allData) {
+      var values = [];
+
+      for (var d = 0; d < allData.length; d++) {
+        var item = allData[d];
+
+        if (data.id == item.id) {
+          continue;
+        }
+
+        values.push(item.id);
+      }
+
+      self.$element.val(values.join(self._valueSeparator));
+      self.$element.trigger('change');
+    });
+  };
+
+  InputData.prototype.query = function (_, params, callback) {
+    var results = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      var matches = this.matches(params, data);
+
+      if (matches !== null) {
+        results.push(matches);
+      }
+    }
+
+    callback({
+      results: results
+    });
+  };
+
+  InputData.prototype.addOptions = function (_, $options) {
+    var options = $.map($options, function ($option) {
+      return $.data($option[0], 'data');
+    });
+
+    this._currentData.push.apply(this._currentData, options);
+  };
+
+  return InputData;
+});
+
+S2.define('select2/compat/matcher',[
+  'jquery'
+], function ($) {
+  function oldMatcher (matcher) {
+    function wrappedMatcher (params, data) {
+      var match = $.extend(true, {}, data);
+
+      if (params.term == null || $.trim(params.term) === '') {
+        return match;
+      }
+
+      if (data.children) {
+        for (var c = data.children.length - 1; c >= 0; c--) {
+          var child = data.children[c];
+
+          // Check if the child object matches
+          // The old matcher returned a boolean true or false
+          var doesMatch = matcher(params.term, child.text, child);
+
+          // If the child didn't match, pop it off
+          if (!doesMatch) {
+            match.children.splice(c, 1);
+          }
+        }
+
+        if (match.children.length > 0) {
+          return match;
+        }
+      }
+
+      if (matcher(params.term, data.text, data)) {
+        return match;
+      }
+
+      return null;
+    }
+
+    return wrappedMatcher;
+  }
+
+  return oldMatcher;
+});
+
+S2.define('select2/compat/query',[
+
+], function () {
+  function Query (decorated, $element, options) {
+    if (options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `query` option has been deprecated in favor of a ' +
+        'custom data adapter that overrides the `query` method. Support ' +
+        'will be removed for the `query` option in future versions of ' +
+        'Select2.'
+      );
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  Query.prototype.query = function (_, params, callback) {
+    params.callback = callback;
+
+    var query = this.options.get('query');
+
+    query.call(null, params);
+  };
+
+  return Query;
+});
+
+S2.define('select2/dropdown/attachContainer',[
+
+], function () {
+  function AttachContainer (decorated, $element, options) {
+    decorated.call(this, $element, options);
+  }
+
+  AttachContainer.prototype.position =
+    function (decorated, $dropdown, $container) {
+    var $dropdownContainer = $container.find('.dropdown-wrapper');
+    $dropdownContainer.append($dropdown);
+
+    $dropdown.addClass('select2-dropdown--below');
+    $container.addClass('select2-container--below');
+  };
+
+  return AttachContainer;
+});
+
+S2.define('select2/dropdown/stopPropagation',[
+
+], function () {
+  function StopPropagation () { }
+
+  StopPropagation.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    var stoppedEvents = [
+    'blur',
+    'change',
+    'click',
+    'dblclick',
+    'focus',
+    'focusin',
+    'focusout',
+    'input',
+    'keydown',
+    'keyup',
+    'keypress',
+    'mousedown',
+    'mouseenter',
+    'mouseleave',
+    'mousemove',
+    'mouseover',
+    'mouseup',
+    'search',
+    'touchend',
+    'touchstart'
+    ];
+
+    this.$dropdown.on(stoppedEvents.join(' '), function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  return StopPropagation;
+});
+
+S2.define('select2/selection/stopPropagation',[
+
+], function () {
+  function StopPropagation () { }
+
+  StopPropagation.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    var stoppedEvents = [
+      'blur',
+      'change',
+      'click',
+      'dblclick',
+      'focus',
+      'focusin',
+      'focusout',
+      'input',
+      'keydown',
+      'keyup',
+      'keypress',
+      'mousedown',
+      'mouseenter',
+      'mouseleave',
+      'mousemove',
+      'mouseover',
+      'mouseup',
+      'search',
+      'touchend',
+      'touchstart'
+    ];
+
+    this.$selection.on(stoppedEvents.join(' '), function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  return StopPropagation;
+});
+
+/*!
+ * jQuery Mousewheel 3.1.13
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ */
+
+(function (factory) {
+    if ( typeof S2.define === 'function' && S2.define.amd ) {
+        // AMD. Register as an anonymous module.
+        S2.define('jquery-mousewheel',['jquery'], factory);
+    } else if (typeof exports === 'object') {
+        // Node/CommonJS style for Browserify
+        module.exports = factory;
+    } else {
+        // Browser globals
+        factory(jQuery);
+    }
+}(function ($) {
+
+    var toFix  = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
+        toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ?
+                    ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
+        slice  = Array.prototype.slice,
+        nullLowestDeltaTimeout, lowestDelta;
+
+    if ( $.event.fixHooks ) {
+        for ( var i = toFix.length; i; ) {
+            $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
+        }
+    }
+
+    var special = $.event.special.mousewheel = {
+        version: '3.1.12',
+
+        setup: function() {
+            if ( this.addEventListener ) {
+                for ( var i = toBind.length; i; ) {
+                    this.addEventListener( toBind[--i], handler, false );
+                }
+            } else {
+                this.onmousewheel = handler;
+            }
+            // Store the line height and page height for this particular element
+            $.data(this, 'mousewheel-line-height', special.getLineHeight(this));
+            $.data(this, 'mousewheel-page-height', special.getPageHeight(this));
+        },
+
+        teardown: function() {
+            if ( this.removeEventListener ) {
+                for ( var i = toBind.length; i; ) {
+                    this.removeEventListener( toBind[--i], handler, false );
+                }
+            } else {
+                this.onmousewheel = null;
+            }
+            // Clean up the data we added to the element
+            $.removeData(this, 'mousewheel-line-height');
+            $.removeData(this, 'mousewheel-page-height');
+        },
+
+        getLineHeight: function(elem) {
+            var $elem = $(elem),
+                $parent = $elem['offsetParent' in $.fn ? 'offsetParent' : 'parent']();
+            if (!$parent.length) {
+                $parent = $('body');
+            }
+            return parseInt($parent.css('fontSize'), 10) || parseInt($elem.css('fontSize'), 10) || 16;
+        },
+
+        getPageHeight: function(elem) {
+            return $(elem).height();
+        },
+
+        settings: {
+            adjustOldDeltas: true, // see shouldAdjustOldDeltas() below
+            normalizeOffset: true  // calls getBoundingClientRect for each event
+        }
+    };
+
+    $.fn.extend({
+        mousewheel: function(fn) {
+            return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel');
+        },
+
+        unmousewheel: function(fn) {
+            return this.unbind('mousewheel', fn);
+        }
+    });
+
+
+    function handler(event) {
+        var orgEvent   = event || window.event,
+            args       = slice.call(arguments, 1),
+            delta      = 0,
+            deltaX     = 0,
+            deltaY     = 0,
+            absDelta   = 0,
+            offsetX    = 0,
+            offsetY    = 0;
+        event = $.event.fix(orgEvent);
+        event.type = 'mousewheel';
+
+        // Old school scrollwheel delta
+        if ( 'detail'      in orgEvent ) { deltaY = orgEvent.detail * -1;      }
+        if ( 'wheelDelta'  in orgEvent ) { deltaY = orgEvent.wheelDelta;       }
+        if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY;      }
+        if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; }
+
+        // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
+        if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
+            deltaX = deltaY * -1;
+            deltaY = 0;
+        }
+
+        // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
+        delta = deltaY === 0 ? deltaX : deltaY;
+
+        // New school wheel delta (wheel event)
+        if ( 'deltaY' in orgEvent ) {
+            deltaY = orgEvent.deltaY * -1;
+            delta  = deltaY;
+        }
+        if ( 'deltaX' in orgEvent ) {
+            deltaX = orgEvent.deltaX;
+            if ( deltaY === 0 ) { delta  = deltaX * -1; }
+        }
+
+        // No change actually happened, no reason to go any further
+        if ( deltaY === 0 && deltaX === 0 ) { return; }
+
+        // Need to convert lines and pages to pixels if we aren't already in pixels
+        // There are three delta modes:
+        //   * deltaMode 0 is by pixels, nothing to do
+        //   * deltaMode 1 is by lines
+        //   * deltaMode 2 is by pages
+        if ( orgEvent.deltaMode === 1 ) {
+            var lineHeight = $.data(this, 'mousewheel-line-height');
+            delta  *= lineHeight;
+            deltaY *= lineHeight;
+            deltaX *= lineHeight;
+        } else if ( orgEvent.deltaMode === 2 ) {
+            var pageHeight = $.data(this, 'mousewheel-page-height');
+            delta  *= pageHeight;
+            deltaY *= pageHeight;
+            deltaX *= pageHeight;
+        }
+
+        // Store lowest absolute delta to normalize the delta values
+        absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
+
+        if ( !lowestDelta || absDelta < lowestDelta ) {
+            lowestDelta = absDelta;
+
+            // Adjust older deltas if necessary
+            if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
+                lowestDelta /= 40;
+            }
+        }
+
+        // Adjust older deltas if necessary
+        if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
+            // Divide all the things by 40!
+            delta  /= 40;
+            deltaX /= 40;
+            deltaY /= 40;
+        }
+
+        // Get a whole, normalized value for the deltas
+        delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
+        deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
+        deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
+
+        // Normalise offsetX and offsetY properties
+        if ( special.settings.normalizeOffset && this.getBoundingClientRect ) {
+            var boundingRect = this.getBoundingClientRect();
+            offsetX = event.clientX - boundingRect.left;
+            offsetY = event.clientY - boundingRect.top;
+        }
+
+        // Add information to the event object
+        event.deltaX = deltaX;
+        event.deltaY = deltaY;
+        event.deltaFactor = lowestDelta;
+        event.offsetX = offsetX;
+        event.offsetY = offsetY;
+        // Go ahead and set deltaMode to 0 since we converted to pixels
+        // Although this is a little odd since we overwrite the deltaX/Y
+        // properties with normalized deltas.
+        event.deltaMode = 0;
+
+        // Add event and delta to the front of the arguments
+        args.unshift(event, delta, deltaX, deltaY);
+
+        // Clearout lowestDelta after sometime to better
+        // handle multiple device types that give different
+        // a different lowestDelta
+        // Ex: trackpad = 3 and mouse wheel = 120
+        if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
+        nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
+
+        return ($.event.dispatch || $.event.handle).apply(this, args);
+    }
+
+    function nullLowestDelta() {
+        lowestDelta = null;
+    }
+
+    function shouldAdjustOldDeltas(orgEvent, absDelta) {
+        // If this is an older event and the delta is divisable by 120,
+        // then we are assuming that the browser is treating this as an
+        // older mouse wheel event and that we should divide the deltas
+        // by 40 to try and get a more usable deltaFactor.
+        // Side note, this actually impacts the reported scroll distance
+        // in older browsers and can cause scrolling to be slower than native.
+        // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
+        return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
+    }
+
+}));
+
+S2.define('jquery.select2',[
+  'jquery',
+  'jquery-mousewheel',
+
+  './select2/core',
+  './select2/defaults'
+], function ($, _, Select2, Defaults) {
+  if ($.fn.select2 == null) {
+    // All methods that should return the element
+    var thisMethods = ['open', 'close', 'destroy'];
+
+    $.fn.select2 = function (options) {
+      options = options || {};
+
+      if (typeof options === 'object') {
+        this.each(function () {
+          var instanceOptions = $.extend(true, {}, options);
+
+          var instance = new Select2($(this), instanceOptions);
+        });
+
+        return this;
+      } else if (typeof options === 'string') {
+        var ret;
+        var args = Array.prototype.slice.call(arguments, 1);
+
+        this.each(function () {
+          var instance = $(this).data('select2');
+
+          if (instance == null && window.console && console.error) {
+            console.error(
+              'The select2(\'' + options + '\') method was called on an ' +
+              'element that is not using Select2.'
+            );
+          }
+
+          ret = instance[options].apply(instance, args);
+        });
+
+        // Check if we should be returning `this`
+        if ($.inArray(options, thisMethods) > -1) {
+          return this;
+        }
+
+        return ret;
+      } else {
+        throw new Error('Invalid arguments for Select2: ' + options);
+      }
+    };
+  }
+
+  if ($.fn.select2.defaults == null) {
+    $.fn.select2.defaults = Defaults;
+  }
+
+  return Select2;
+});
+
+  // Return the AMD loader configuration so it can be used outside of this file
+  return {
+    define: S2.define,
+    require: S2.require
+  };
+}());
+
+  // Autoload the jQuery bindings
+  // We know that all of the modules exist above this, so we're safe
+  var select2 = S2.require('jquery.select2');
+
+  // Hold the AMD module references on the jQuery function that was just loaded
+  // This allows Select2 to use the internal loader outside of this file, such
+  // as in the language files.
+  jQuery.fn.select2.amd = S2;
+
+  // Return the Select2 instance for anyone who is importing it.
+  return select2;
+}));

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
netbox/project-static/select2-4.0.5/js/select2.full.min.js


+ 5746 - 0
netbox/project-static/select2-4.0.5/js/select2.js

@@ -0,0 +1,5746 @@
+/*!
+ * Select2 4.0.5
+ * https://select2.github.io
+ *
+ * Released under the MIT license
+ * https://github.com/select2/select2/blob/master/LICENSE.md
+ */
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(['jquery'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    // Node/CommonJS
+    module.exports = function (root, jQuery) {
+      if (jQuery === undefined) {
+        // require('jQuery') returns a factory that requires window to
+        // build a jQuery instance, we normalize how we use modules
+        // that require this pattern but the window provided is a noop
+        // if it's defined (how jquery works)
+        if (typeof window !== 'undefined') {
+          jQuery = require('jquery');
+        }
+        else {
+          jQuery = require('jquery')(root);
+        }
+      }
+      factory(jQuery);
+      return jQuery;
+    };
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+} (function (jQuery) {
+  // This is needed so we can catch the AMD loader configuration and use it
+  // The inner file should be wrapped (by `banner.start.js`) in a function that
+  // returns the AMD loader references.
+  var S2 =(function () {
+  // Restore the Select2 AMD loader so it can be used
+  // Needed mostly in the language files, where the loader is not inserted
+  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
+    var S2 = jQuery.fn.select2.amd;
+  }
+var S2;(function () { if (!S2 || !S2.requirejs) {
+if (!S2) { S2 = {}; } else { require = S2; }
+/**
+ * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, http://github.com/requirejs/almond/LICENSE
+ */
+//Going sloppy to avoid 'use strict' string cost, but strict practices should
+//be followed.
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+    var main, req, makeMap, handlers,
+        defined = {},
+        waiting = {},
+        config = {},
+        defining = {},
+        hasOwn = Object.prototype.hasOwnProperty,
+        aps = [].slice,
+        jsSuffixRegExp = /\.js$/;
+
+    function hasProp(obj, prop) {
+        return hasOwn.call(obj, prop);
+    }
+
+    /**
+     * Given a relative module name, like ./something, normalize it to
+     * a real name that can be mapped to a path.
+     * @param {String} name the relative name
+     * @param {String} baseName a real name that the name arg is relative
+     * to.
+     * @returns {String} normalized name
+     */
+    function normalize(name, baseName) {
+        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
+            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
+            baseParts = baseName && baseName.split("/"),
+            map = config.map,
+            starMap = (map && map['*']) || {};
+
+        //Adjust any relative paths.
+        if (name) {
+            name = name.split('/');
+            lastIndex = name.length - 1;
+
+            // If wanting node ID compatibility, strip .js from end
+            // of IDs. Have to do this here, and not in nameToUrl
+            // because node allows either .js or non .js to map
+            // to same file.
+            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+            }
+
+            // Starts with a '.' so need the baseName
+            if (name[0].charAt(0) === '.' && baseParts) {
+                //Convert baseName to array, and lop off the last part,
+                //so that . matches that 'directory' and not name of the baseName's
+                //module. For instance, baseName of 'one/two/three', maps to
+                //'one/two/three.js', but we want the directory, 'one/two' for
+                //this normalization.
+                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+                name = normalizedBaseParts.concat(name);
+            }
+
+            //start trimDots
+            for (i = 0; i < name.length; i++) {
+                part = name[i];
+                if (part === '.') {
+                    name.splice(i, 1);
+                    i -= 1;
+                } else if (part === '..') {
+                    // If at the start, or previous value is still ..,
+                    // keep them so that when converted to a path it may
+                    // still work when converted to a path, even though
+                    // as an ID it is less than ideal. In larger point
+                    // releases, may be better to just kick out an error.
+                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
+                        continue;
+                    } else if (i > 0) {
+                        name.splice(i - 1, 2);
+                        i -= 2;
+                    }
+                }
+            }
+            //end trimDots
+
+            name = name.join('/');
+        }
+
+        //Apply map config if available.
+        if ((baseParts || starMap) && map) {
+            nameParts = name.split('/');
+
+            for (i = nameParts.length; i > 0; i -= 1) {
+                nameSegment = nameParts.slice(0, i).join("/");
+
+                if (baseParts) {
+                    //Find the longest baseName segment match in the config.
+                    //So, do joins on the biggest to smallest lengths of baseParts.
+                    for (j = baseParts.length; j > 0; j -= 1) {
+                        mapValue = map[baseParts.slice(0, j).join('/')];
+
+                        //baseName segment has  config, find if it has one for
+                        //this name.
+                        if (mapValue) {
+                            mapValue = mapValue[nameSegment];
+                            if (mapValue) {
+                                //Match, update name to the new value.
+                                foundMap = mapValue;
+                                foundI = i;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (foundMap) {
+                    break;
+                }
+
+                //Check for a star map match, but just hold on to it,
+                //if there is a shorter segment match later in a matching
+                //config, then favor over this star map.
+                if (!foundStarMap && starMap && starMap[nameSegment]) {
+                    foundStarMap = starMap[nameSegment];
+                    starI = i;
+                }
+            }
+
+            if (!foundMap && foundStarMap) {
+                foundMap = foundStarMap;
+                foundI = starI;
+            }
+
+            if (foundMap) {
+                nameParts.splice(0, foundI, foundMap);
+                name = nameParts.join('/');
+            }
+        }
+
+        return name;
+    }
+
+    function makeRequire(relName, forceSync) {
+        return function () {
+            //A version of a require function that passes a moduleName
+            //value for items that may need to
+            //look up paths relative to the moduleName
+            var args = aps.call(arguments, 0);
+
+            //If first arg is not require('string'), and there is only
+            //one arg, it is the array form without a callback. Insert
+            //a null so that the following concat is correct.
+            if (typeof args[0] !== 'string' && args.length === 1) {
+                args.push(null);
+            }
+            return req.apply(undef, args.concat([relName, forceSync]));
+        };
+    }
+
+    function makeNormalize(relName) {
+        return function (name) {
+            return normalize(name, relName);
+        };
+    }
+
+    function makeLoad(depName) {
+        return function (value) {
+            defined[depName] = value;
+        };
+    }
+
+    function callDep(name) {
+        if (hasProp(waiting, name)) {
+            var args = waiting[name];
+            delete waiting[name];
+            defining[name] = true;
+            main.apply(undef, args);
+        }
+
+        if (!hasProp(defined, name) && !hasProp(defining, name)) {
+            throw new Error('No ' + name);
+        }
+        return defined[name];
+    }
+
+    //Turns a plugin!resource to [plugin, resource]
+    //with the plugin being undefined if the name
+    //did not have a plugin prefix.
+    function splitPrefix(name) {
+        var prefix,
+            index = name ? name.indexOf('!') : -1;
+        if (index > -1) {
+            prefix = name.substring(0, index);
+            name = name.substring(index + 1, name.length);
+        }
+        return [prefix, name];
+    }
+
+    //Creates a parts array for a relName where first part is plugin ID,
+    //second part is resource ID. Assumes relName has already been normalized.
+    function makeRelParts(relName) {
+        return relName ? splitPrefix(relName) : [];
+    }
+
+    /**
+     * Makes a name map, normalizing the name, and using a plugin
+     * for normalization if necessary. Grabs a ref to plugin
+     * too, as an optimization.
+     */
+    makeMap = function (name, relParts) {
+        var plugin,
+            parts = splitPrefix(name),
+            prefix = parts[0],
+            relResourceName = relParts[1];
+
+        name = parts[1];
+
+        if (prefix) {
+            prefix = normalize(prefix, relResourceName);
+            plugin = callDep(prefix);
+        }
+
+        //Normalize according
+        if (prefix) {
+            if (plugin && plugin.normalize) {
+                name = plugin.normalize(name, makeNormalize(relResourceName));
+            } else {
+                name = normalize(name, relResourceName);
+            }
+        } else {
+            name = normalize(name, relResourceName);
+            parts = splitPrefix(name);
+            prefix = parts[0];
+            name = parts[1];
+            if (prefix) {
+                plugin = callDep(prefix);
+            }
+        }
+
+        //Using ridiculous property names for space reasons
+        return {
+            f: prefix ? prefix + '!' + name : name, //fullName
+            n: name,
+            pr: prefix,
+            p: plugin
+        };
+    };
+
+    function makeConfig(name) {
+        return function () {
+            return (config && config.config && config.config[name]) || {};
+        };
+    }
+
+    handlers = {
+        require: function (name) {
+            return makeRequire(name);
+        },
+        exports: function (name) {
+            var e = defined[name];
+            if (typeof e !== 'undefined') {
+                return e;
+            } else {
+                return (defined[name] = {});
+            }
+        },
+        module: function (name) {
+            return {
+                id: name,
+                uri: '',
+                exports: defined[name],
+                config: makeConfig(name)
+            };
+        }
+    };
+
+    main = function (name, deps, callback, relName) {
+        var cjsModule, depName, ret, map, i, relParts,
+            args = [],
+            callbackType = typeof callback,
+            usingExports;
+
+        //Use name if no relName
+        relName = relName || name;
+        relParts = makeRelParts(relName);
+
+        //Call the callback to define the module, if necessary.
+        if (callbackType === 'undefined' || callbackType === 'function') {
+            //Pull out the defined dependencies and pass the ordered
+            //values to the callback.
+            //Default to [require, exports, module] if no deps
+            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
+            for (i = 0; i < deps.length; i += 1) {
+                map = makeMap(deps[i], relParts);
+                depName = map.f;
+
+                //Fast path CommonJS standard dependencies.
+                if (depName === "require") {
+                    args[i] = handlers.require(name);
+                } else if (depName === "exports") {
+                    //CommonJS module spec 1.1
+                    args[i] = handlers.exports(name);
+                    usingExports = true;
+                } else if (depName === "module") {
+                    //CommonJS module spec 1.1
+                    cjsModule = args[i] = handlers.module(name);
+                } else if (hasProp(defined, depName) ||
+                           hasProp(waiting, depName) ||
+                           hasProp(defining, depName)) {
+                    args[i] = callDep(depName);
+                } else if (map.p) {
+                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+                    args[i] = defined[depName];
+                } else {
+                    throw new Error(name + ' missing ' + depName);
+                }
+            }
+
+            ret = callback ? callback.apply(defined[name], args) : undefined;
+
+            if (name) {
+                //If setting exports via "module" is in play,
+                //favor that over return value and exports. After that,
+                //favor a non-undefined return value over exports use.
+                if (cjsModule && cjsModule.exports !== undef &&
+                        cjsModule.exports !== defined[name]) {
+                    defined[name] = cjsModule.exports;
+                } else if (ret !== undef || !usingExports) {
+                    //Use the return value from the function.
+                    defined[name] = ret;
+                }
+            }
+        } else if (name) {
+            //May just be an object definition for the module. Only
+            //worry about defining if have a module name.
+            defined[name] = callback;
+        }
+    };
+
+    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
+        if (typeof deps === "string") {
+            if (handlers[deps]) {
+                //callback in this case is really relName
+                return handlers[deps](callback);
+            }
+            //Just return the module wanted. In this scenario, the
+            //deps arg is the module name, and second arg (if passed)
+            //is just the relName.
+            //Normalize module name, if it contains . or ..
+            return callDep(makeMap(deps, makeRelParts(callback)).f);
+        } else if (!deps.splice) {
+            //deps is a config object, not an array.
+            config = deps;
+            if (config.deps) {
+                req(config.deps, config.callback);
+            }
+            if (!callback) {
+                return;
+            }
+
+            if (callback.splice) {
+                //callback is an array, which means it is a dependency list.
+                //Adjust args if there are dependencies
+                deps = callback;
+                callback = relName;
+                relName = null;
+            } else {
+                deps = undef;
+            }
+        }
+
+        //Support require(['a'])
+        callback = callback || function () {};
+
+        //If relName is a function, it is an errback handler,
+        //so remove it.
+        if (typeof relName === 'function') {
+            relName = forceSync;
+            forceSync = alt;
+        }
+
+        //Simulate async callback;
+        if (forceSync) {
+            main(undef, deps, callback, relName);
+        } else {
+            //Using a non-zero value because of concern for what old browsers
+            //do, and latest browsers "upgrade" to 4 if lower value is used:
+            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
+            //If want a value immediately, use require('id') instead -- something
+            //that works in almond on the global level, but not guaranteed and
+            //unlikely to work in other AMD implementations.
+            setTimeout(function () {
+                main(undef, deps, callback, relName);
+            }, 4);
+        }
+
+        return req;
+    };
+
+    /**
+     * Just drops the config on the floor, but returns req in case
+     * the config return value is used.
+     */
+    req.config = function (cfg) {
+        return req(cfg);
+    };
+
+    /**
+     * Expose module registry for debugging and tooling
+     */
+    requirejs._defined = defined;
+
+    define = function (name, deps, callback) {
+        if (typeof name !== 'string') {
+            throw new Error('See almond README: incorrect module build, no module name');
+        }
+
+        //This module may not have dependencies
+        if (!deps.splice) {
+            //deps is not an array, so probably means
+            //an object literal or factory function for
+            //the value. Adjust args.
+            callback = deps;
+            deps = [];
+        }
+
+        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
+            waiting[name] = [name, deps, callback];
+        }
+    };
+
+    define.amd = {
+        jQuery: true
+    };
+}());
+
+S2.requirejs = requirejs;S2.require = require;S2.define = define;
+}
+}());
+S2.define("almond", function(){});
+
+/* global jQuery:false, $:false */
+S2.define('jquery',[],function () {
+  var _$ = jQuery || $;
+
+  if (_$ == null && console && console.error) {
+    console.error(
+      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
+      'found. Make sure that you are including jQuery before Select2 on your ' +
+      'web page.'
+    );
+  }
+
+  return _$;
+});
+
+S2.define('select2/utils',[
+  'jquery'
+], function ($) {
+  var Utils = {};
+
+  Utils.Extend = function (ChildClass, SuperClass) {
+    var __hasProp = {}.hasOwnProperty;
+
+    function BaseConstructor () {
+      this.constructor = ChildClass;
+    }
+
+    for (var key in SuperClass) {
+      if (__hasProp.call(SuperClass, key)) {
+        ChildClass[key] = SuperClass[key];
+      }
+    }
+
+    BaseConstructor.prototype = SuperClass.prototype;
+    ChildClass.prototype = new BaseConstructor();
+    ChildClass.__super__ = SuperClass.prototype;
+
+    return ChildClass;
+  };
+
+  function getMethods (theClass) {
+    var proto = theClass.prototype;
+
+    var methods = [];
+
+    for (var methodName in proto) {
+      var m = proto[methodName];
+
+      if (typeof m !== 'function') {
+        continue;
+      }
+
+      if (methodName === 'constructor') {
+        continue;
+      }
+
+      methods.push(methodName);
+    }
+
+    return methods;
+  }
+
+  Utils.Decorate = function (SuperClass, DecoratorClass) {
+    var decoratedMethods = getMethods(DecoratorClass);
+    var superMethods = getMethods(SuperClass);
+
+    function DecoratedClass () {
+      var unshift = Array.prototype.unshift;
+
+      var argCount = DecoratorClass.prototype.constructor.length;
+
+      var calledConstructor = SuperClass.prototype.constructor;
+
+      if (argCount > 0) {
+        unshift.call(arguments, SuperClass.prototype.constructor);
+
+        calledConstructor = DecoratorClass.prototype.constructor;
+      }
+
+      calledConstructor.apply(this, arguments);
+    }
+
+    DecoratorClass.displayName = SuperClass.displayName;
+
+    function ctr () {
+      this.constructor = DecoratedClass;
+    }
+
+    DecoratedClass.prototype = new ctr();
+
+    for (var m = 0; m < superMethods.length; m++) {
+        var superMethod = superMethods[m];
+
+        DecoratedClass.prototype[superMethod] =
+          SuperClass.prototype[superMethod];
+    }
+
+    var calledMethod = function (methodName) {
+      // Stub out the original method if it's not decorating an actual method
+      var originalMethod = function () {};
+
+      if (methodName in DecoratedClass.prototype) {
+        originalMethod = DecoratedClass.prototype[methodName];
+      }
+
+      var decoratedMethod = DecoratorClass.prototype[methodName];
+
+      return function () {
+        var unshift = Array.prototype.unshift;
+
+        unshift.call(arguments, originalMethod);
+
+        return decoratedMethod.apply(this, arguments);
+      };
+    };
+
+    for (var d = 0; d < decoratedMethods.length; d++) {
+      var decoratedMethod = decoratedMethods[d];
+
+      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
+    }
+
+    return DecoratedClass;
+  };
+
+  var Observable = function () {
+    this.listeners = {};
+  };
+
+  Observable.prototype.on = function (event, callback) {
+    this.listeners = this.listeners || {};
+
+    if (event in this.listeners) {
+      this.listeners[event].push(callback);
+    } else {
+      this.listeners[event] = [callback];
+    }
+  };
+
+  Observable.prototype.trigger = function (event) {
+    var slice = Array.prototype.slice;
+    var params = slice.call(arguments, 1);
+
+    this.listeners = this.listeners || {};
+
+    // Params should always come in as an array
+    if (params == null) {
+      params = [];
+    }
+
+    // If there are no arguments to the event, use a temporary object
+    if (params.length === 0) {
+      params.push({});
+    }
+
+    // Set the `_type` of the first object to the event
+    params[0]._type = event;
+
+    if (event in this.listeners) {
+      this.invoke(this.listeners[event], slice.call(arguments, 1));
+    }
+
+    if ('*' in this.listeners) {
+      this.invoke(this.listeners['*'], arguments);
+    }
+  };
+
+  Observable.prototype.invoke = function (listeners, params) {
+    for (var i = 0, len = listeners.length; i < len; i++) {
+      listeners[i].apply(this, params);
+    }
+  };
+
+  Utils.Observable = Observable;
+
+  Utils.generateChars = function (length) {
+    var chars = '';
+
+    for (var i = 0; i < length; i++) {
+      var randomChar = Math.floor(Math.random() * 36);
+      chars += randomChar.toString(36);
+    }
+
+    return chars;
+  };
+
+  Utils.bind = function (func, context) {
+    return function () {
+      func.apply(context, arguments);
+    };
+  };
+
+  Utils._convertData = function (data) {
+    for (var originalKey in data) {
+      var keys = originalKey.split('-');
+
+      var dataLevel = data;
+
+      if (keys.length === 1) {
+        continue;
+      }
+
+      for (var k = 0; k < keys.length; k++) {
+        var key = keys[k];
+
+        // Lowercase the first letter
+        // By default, dash-separated becomes camelCase
+        key = key.substring(0, 1).toLowerCase() + key.substring(1);
+
+        if (!(key in dataLevel)) {
+          dataLevel[key] = {};
+        }
+
+        if (k == keys.length - 1) {
+          dataLevel[key] = data[originalKey];
+        }
+
+        dataLevel = dataLevel[key];
+      }
+
+      delete data[originalKey];
+    }
+
+    return data;
+  };
+
+  Utils.hasScroll = function (index, el) {
+    // Adapted from the function created by @ShadowScripter
+    // and adapted by @BillBarry on the Stack Exchange Code Review website.
+    // The original code can be found at
+    // http://codereview.stackexchange.com/q/13338
+    // and was designed to be used with the Sizzle selector engine.
+
+    var $el = $(el);
+    var overflowX = el.style.overflowX;
+    var overflowY = el.style.overflowY;
+
+    //Check both x and y declarations
+    if (overflowX === overflowY &&
+        (overflowY === 'hidden' || overflowY === 'visible')) {
+      return false;
+    }
+
+    if (overflowX === 'scroll' || overflowY === 'scroll') {
+      return true;
+    }
+
+    return ($el.innerHeight() < el.scrollHeight ||
+      $el.innerWidth() < el.scrollWidth);
+  };
+
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    // Do not try to escape the markup if it's not a string
+    if (typeof markup !== 'string') {
+      return markup;
+    }
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
+  // Append an array of jQuery nodes to a given element.
+  Utils.appendMany = function ($element, $nodes) {
+    // jQuery 1.7.x does not support $.fn.append() with an array
+    // Fall back to a jQuery object collection using $.fn.add()
+    if ($.fn.jquery.substr(0, 3) === '1.7') {
+      var $jqNodes = $();
+
+      $.map($nodes, function (node) {
+        $jqNodes = $jqNodes.add(node);
+      });
+
+      $nodes = $jqNodes;
+    }
+
+    $element.append($nodes);
+  };
+
+  return Utils;
+});
+
+S2.define('select2/results',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Results ($element, options, dataAdapter) {
+    this.$element = $element;
+    this.data = dataAdapter;
+    this.options = options;
+
+    Results.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Results, Utils.Observable);
+
+  Results.prototype.render = function () {
+    var $results = $(
+      '<ul class="select2-results__options" role="tree"></ul>'
+    );
+
+    if (this.options.get('multiple')) {
+      $results.attr('aria-multiselectable', 'true');
+    }
+
+    this.$results = $results;
+
+    return $results;
+  };
+
+  Results.prototype.clear = function () {
+    this.$results.empty();
+  };
+
+  Results.prototype.displayMessage = function (params) {
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    this.clear();
+    this.hideLoading();
+
+    var $message = $(
+      '<li role="treeitem" aria-live="assertive"' +
+      ' class="select2-results__option"></li>'
+    );
+
+    var message = this.options.get('translations').get(params.message);
+
+    $message.append(
+      escapeMarkup(
+        message(params.args)
+      )
+    );
+
+    $message[0].className += ' select2-results__message';
+
+    this.$results.append($message);
+  };
+
+  Results.prototype.hideMessages = function () {
+    this.$results.find('.select2-results__message').remove();
+  };
+
+  Results.prototype.append = function (data) {
+    this.hideLoading();
+
+    var $options = [];
+
+    if (data.results == null || data.results.length === 0) {
+      if (this.$results.children().length === 0) {
+        this.trigger('results:message', {
+          message: 'noResults'
+        });
+      }
+
+      return;
+    }
+
+    data.results = this.sort(data.results);
+
+    for (var d = 0; d < data.results.length; d++) {
+      var item = data.results[d];
+
+      var $option = this.option(item);
+
+      $options.push($option);
+    }
+
+    this.$results.append($options);
+  };
+
+  Results.prototype.position = function ($results, $dropdown) {
+    var $resultsContainer = $dropdown.find('.select2-results');
+    $resultsContainer.append($results);
+  };
+
+  Results.prototype.sort = function (data) {
+    var sorter = this.options.get('sorter');
+
+    return sorter(data);
+  };
+
+  Results.prototype.highlightFirstItem = function () {
+    var $options = this.$results
+      .find('.select2-results__option[aria-selected]');
+
+    var $selected = $options.filter('[aria-selected=true]');
+
+    // Check if there are any selected options
+    if ($selected.length > 0) {
+      // If there are selected options, highlight the first
+      $selected.first().trigger('mouseenter');
+    } else {
+      // If there are no selected options, highlight the first option
+      // in the dropdown
+      $options.first().trigger('mouseenter');
+    }
+
+    this.ensureHighlightVisible();
+  };
+
+  Results.prototype.setClasses = function () {
+    var self = this;
+
+    this.data.current(function (selected) {
+      var selectedIds = $.map(selected, function (s) {
+        return s.id.toString();
+      });
+
+      var $options = self.$results
+        .find('.select2-results__option[aria-selected]');
+
+      $options.each(function () {
+        var $option = $(this);
+
+        var item = $.data(this, 'data');
+
+        // id needs to be converted to a string when comparing
+        var id = '' + item.id;
+
+        if ((item.element != null && item.element.selected) ||
+            (item.element == null && $.inArray(id, selectedIds) > -1)) {
+          $option.attr('aria-selected', 'true');
+        } else {
+          $option.attr('aria-selected', 'false');
+        }
+      });
+
+    });
+  };
+
+  Results.prototype.showLoading = function (params) {
+    this.hideLoading();
+
+    var loadingMore = this.options.get('translations').get('searching');
+
+    var loading = {
+      disabled: true,
+      loading: true,
+      text: loadingMore(params)
+    };
+    var $loading = this.option(loading);
+    $loading.className += ' loading-results';
+
+    this.$results.prepend($loading);
+  };
+
+  Results.prototype.hideLoading = function () {
+    this.$results.find('.loading-results').remove();
+  };
+
+  Results.prototype.option = function (data) {
+    var option = document.createElement('li');
+    option.className = 'select2-results__option';
+
+    var attrs = {
+      'role': 'treeitem',
+      'aria-selected': 'false'
+    };
+
+    if (data.disabled) {
+      delete attrs['aria-selected'];
+      attrs['aria-disabled'] = 'true';
+    }
+
+    if (data.id == null) {
+      delete attrs['aria-selected'];
+    }
+
+    if (data._resultId != null) {
+      option.id = data._resultId;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    if (data.children) {
+      attrs.role = 'group';
+      attrs['aria-label'] = data.text;
+      delete attrs['aria-selected'];
+    }
+
+    for (var attr in attrs) {
+      var val = attrs[attr];
+
+      option.setAttribute(attr, val);
+    }
+
+    if (data.children) {
+      var $option = $(option);
+
+      var label = document.createElement('strong');
+      label.className = 'select2-results__group';
+
+      var $label = $(label);
+      this.template(data, label);
+
+      var $children = [];
+
+      for (var c = 0; c < data.children.length; c++) {
+        var child = data.children[c];
+
+        var $child = this.option(child);
+
+        $children.push($child);
+      }
+
+      var $childrenContainer = $('<ul></ul>', {
+        'class': 'select2-results__options select2-results__options--nested'
+      });
+
+      $childrenContainer.append($children);
+
+      $option.append(label);
+      $option.append($childrenContainer);
+    } else {
+      this.template(data, option);
+    }
+
+    $.data(option, 'data', data);
+
+    return option;
+  };
+
+  Results.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-results';
+
+    this.$results.attr('id', id);
+
+    container.on('results:all', function (params) {
+      self.clear();
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+        self.highlightFirstItem();
+      }
+    });
+
+    container.on('results:append', function (params) {
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+      }
+    });
+
+    container.on('query', function (params) {
+      self.hideMessages();
+      self.showLoading(params);
+    });
+
+    container.on('select', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+      self.highlightFirstItem();
+    });
+
+    container.on('unselect', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+      self.highlightFirstItem();
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expended="true"
+      self.$results.attr('aria-expanded', 'true');
+      self.$results.attr('aria-hidden', 'false');
+
+      self.setClasses();
+      self.ensureHighlightVisible();
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expended="false"
+      self.$results.attr('aria-expanded', 'false');
+      self.$results.attr('aria-hidden', 'true');
+      self.$results.removeAttr('aria-activedescendant');
+    });
+
+    container.on('results:toggle', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      $highlighted.trigger('mouseup');
+    });
+
+    container.on('results:select', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      var data = $highlighted.data('data');
+
+      if ($highlighted.attr('aria-selected') == 'true') {
+        self.trigger('close', {});
+      } else {
+        self.trigger('select', {
+          data: data
+        });
+      }
+    });
+
+    container.on('results:previous', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      // If we are already at te top, don't move further
+      if (currentIndex === 0) {
+        return;
+      }
+
+      var nextIndex = currentIndex - 1;
+
+      // If none are highlighted, highlight the first
+      if ($highlighted.length === 0) {
+        nextIndex = 0;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top;
+      var nextTop = $next.offset().top;
+      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextTop - currentOffset < 0) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:next', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      var nextIndex = currentIndex + 1;
+
+      // If we are at the last option, stay there
+      if (nextIndex >= $options.length) {
+        return;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var nextBottom = $next.offset().top + $next.outerHeight(false);
+      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextBottom > currentOffset) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      params.element.addClass('select2-results__option--highlighted');
+    });
+
+    container.on('results:message', function (params) {
+      self.displayMessage(params);
+    });
+
+    if ($.fn.mousewheel) {
+      this.$results.on('mousewheel', function (e) {
+        var top = self.$results.scrollTop();
+
+        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
+
+        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
+        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
+
+        if (isAtTop) {
+          self.$results.scrollTop(0);
+
+          e.preventDefault();
+          e.stopPropagation();
+        } else if (isAtBottom) {
+          self.$results.scrollTop(
+            self.$results.get(0).scrollHeight - self.$results.height()
+          );
+
+          e.preventDefault();
+          e.stopPropagation();
+        }
+      });
+    }
+
+    this.$results.on('mouseup', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var $this = $(this);
+
+      var data = $this.data('data');
+
+      if ($this.attr('aria-selected') === 'true') {
+        if (self.options.get('multiple')) {
+          self.trigger('unselect', {
+            originalEvent: evt,
+            data: data
+          });
+        } else {
+          self.trigger('close', {});
+        }
+
+        return;
+      }
+
+      self.trigger('select', {
+        originalEvent: evt,
+        data: data
+      });
+    });
+
+    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var data = $(this).data('data');
+
+      self.getHighlightedResults()
+          .removeClass('select2-results__option--highlighted');
+
+      self.trigger('results:focus', {
+        data: data,
+        element: $(this)
+      });
+    });
+  };
+
+  Results.prototype.getHighlightedResults = function () {
+    var $highlighted = this.$results
+    .find('.select2-results__option--highlighted');
+
+    return $highlighted;
+  };
+
+  Results.prototype.destroy = function () {
+    this.$results.remove();
+  };
+
+  Results.prototype.ensureHighlightVisible = function () {
+    var $highlighted = this.getHighlightedResults();
+
+    if ($highlighted.length === 0) {
+      return;
+    }
+
+    var $options = this.$results.find('[aria-selected]');
+
+    var currentIndex = $options.index($highlighted);
+
+    var currentOffset = this.$results.offset().top;
+    var nextTop = $highlighted.offset().top;
+    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
+
+    var offsetDelta = nextTop - currentOffset;
+    nextOffset -= $highlighted.outerHeight(false) * 2;
+
+    if (currentIndex <= 2) {
+      this.$results.scrollTop(0);
+    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
+      this.$results.scrollTop(nextOffset);
+    }
+  };
+
+  Results.prototype.template = function (result, container) {
+    var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    var content = template(result, container);
+
+    if (content == null) {
+      container.style.display = 'none';
+    } else if (typeof content === 'string') {
+      container.innerHTML = escapeMarkup(content);
+    } else {
+      $(container).append(content);
+    }
+  };
+
+  return Results;
+});
+
+S2.define('select2/keys',[
+
+], function () {
+  var KEYS = {
+    BACKSPACE: 8,
+    TAB: 9,
+    ENTER: 13,
+    SHIFT: 16,
+    CTRL: 17,
+    ALT: 18,
+    ESC: 27,
+    SPACE: 32,
+    PAGE_UP: 33,
+    PAGE_DOWN: 34,
+    END: 35,
+    HOME: 36,
+    LEFT: 37,
+    UP: 38,
+    RIGHT: 39,
+    DOWN: 40,
+    DELETE: 46
+  };
+
+  return KEYS;
+});
+
+S2.define('select2/selection/base',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function BaseSelection ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    BaseSelection.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseSelection, Utils.Observable);
+
+  BaseSelection.prototype.render = function () {
+    var $selection = $(
+      '<span class="select2-selection" role="combobox" ' +
+      ' aria-haspopup="true" aria-expanded="false">' +
+      '</span>'
+    );
+
+    this._tabindex = 0;
+
+    if (this.$element.data('old-tabindex') != null) {
+      this._tabindex = this.$element.data('old-tabindex');
+    } else if (this.$element.attr('tabindex') != null) {
+      this._tabindex = this.$element.attr('tabindex');
+    }
+
+    $selection.attr('title', this.$element.attr('title'));
+    $selection.attr('tabindex', this._tabindex);
+
+    this.$selection = $selection;
+
+    return $selection;
+  };
+
+  BaseSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-container';
+    var resultsId = container.id + '-results';
+
+    this.container = container;
+
+    this.$selection.on('focus', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('blur', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      if (evt.which === KEYS.SPACE) {
+        evt.preventDefault();
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      self.$selection.attr('aria-activedescendant', params.data._resultId);
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expanded="true"
+      self.$selection.attr('aria-expanded', 'true');
+      self.$selection.attr('aria-owns', resultsId);
+
+      self._attachCloseHandler(container);
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expanded="false"
+      self.$selection.attr('aria-expanded', 'false');
+      self.$selection.removeAttr('aria-activedescendant');
+      self.$selection.removeAttr('aria-owns');
+
+      self.$selection.focus();
+
+      self._detachCloseHandler(container);
+    });
+
+    container.on('enable', function () {
+      self.$selection.attr('tabindex', self._tabindex);
+    });
+
+    container.on('disable', function () {
+      self.$selection.attr('tabindex', '-1');
+    });
+  };
+
+  BaseSelection.prototype._handleBlur = function (evt) {
+    var self = this;
+
+    // This needs to be delayed as the active element is the body when the tab
+    // key is pressed, possibly along with others.
+    window.setTimeout(function () {
+      // Don't trigger `blur` if the focus is still in the selection
+      if (
+        (document.activeElement == self.$selection[0]) ||
+        ($.contains(self.$selection[0], document.activeElement))
+      ) {
+        return;
+      }
+
+      self.trigger('blur', evt);
+    }, 1);
+  };
+
+  BaseSelection.prototype._attachCloseHandler = function (container) {
+    var self = this;
+
+    $(document.body).on('mousedown.select2.' + container.id, function (e) {
+      var $target = $(e.target);
+
+      var $select = $target.closest('.select2');
+
+      var $all = $('.select2.select2-container--open');
+
+      $all.each(function () {
+        var $this = $(this);
+
+        if (this == $select[0]) {
+          return;
+        }
+
+        var $element = $this.data('element');
+
+        $element.select2('close');
+      });
+    });
+  };
+
+  BaseSelection.prototype._detachCloseHandler = function (container) {
+    $(document.body).off('mousedown.select2.' + container.id);
+  };
+
+  BaseSelection.prototype.position = function ($selection, $container) {
+    var $selectionContainer = $container.find('.selection');
+    $selectionContainer.append($selection);
+  };
+
+  BaseSelection.prototype.destroy = function () {
+    this._detachCloseHandler(this.container);
+  };
+
+  BaseSelection.prototype.update = function (data) {
+    throw new Error('The `update` method must be defined in child classes.');
+  };
+
+  return BaseSelection;
+});
+
+S2.define('select2/selection/single',[
+  'jquery',
+  './base',
+  '../utils',
+  '../keys'
+], function ($, BaseSelection, Utils, KEYS) {
+  function SingleSelection () {
+    SingleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(SingleSelection, BaseSelection);
+
+  SingleSelection.prototype.render = function () {
+    var $selection = SingleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--single');
+
+    $selection.html(
+      '<span class="select2-selection__rendered"></span>' +
+      '<span class="select2-selection__arrow" role="presentation">' +
+        '<b role="presentation"></b>' +
+      '</span>'
+    );
+
+    return $selection;
+  };
+
+  SingleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    SingleSelection.__super__.bind.apply(this, arguments);
+
+    var id = container.id + '-container';
+
+    this.$selection.find('.select2-selection__rendered').attr('id', id);
+    this.$selection.attr('aria-labelledby', id);
+
+    this.$selection.on('mousedown', function (evt) {
+      // Only respond to left clicks
+      if (evt.which !== 1) {
+        return;
+      }
+
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on('focus', function (evt) {
+      // User focuses on the container
+    });
+
+    this.$selection.on('blur', function (evt) {
+      // User exits the container
+    });
+
+    container.on('focus', function (evt) {
+      if (!container.isOpen()) {
+        self.$selection.focus();
+      }
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+  };
+
+  SingleSelection.prototype.clear = function () {
+    this.$selection.find('.select2-selection__rendered').empty();
+  };
+
+  SingleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  SingleSelection.prototype.selectionContainer = function () {
+    return $('<span></span>');
+  };
+
+  SingleSelection.prototype.update = function (data) {
+    if (data.length === 0) {
+      this.clear();
+      return;
+    }
+
+    var selection = data[0];
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+    var formatted = this.display(selection, $rendered);
+
+    $rendered.empty().append(formatted);
+    $rendered.prop('title', selection.title || selection.text);
+  };
+
+  return SingleSelection;
+});
+
+S2.define('select2/selection/multiple',[
+  'jquery',
+  './base',
+  '../utils'
+], function ($, BaseSelection, Utils) {
+  function MultipleSelection ($element, options) {
+    MultipleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(MultipleSelection, BaseSelection);
+
+  MultipleSelection.prototype.render = function () {
+    var $selection = MultipleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--multiple');
+
+    $selection.html(
+      '<ul class="select2-selection__rendered"></ul>'
+    );
+
+    return $selection;
+  };
+
+  MultipleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    MultipleSelection.__super__.bind.apply(this, arguments);
+
+    this.$selection.on('click', function (evt) {
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on(
+      'click',
+      '.select2-selection__choice__remove',
+      function (evt) {
+        // Ignore the event if it is disabled
+        if (self.options.get('disabled')) {
+          return;
+        }
+
+        var $remove = $(this);
+        var $selection = $remove.parent();
+
+        var data = $selection.data('data');
+
+        self.trigger('unselect', {
+          originalEvent: evt,
+          data: data
+        });
+      }
+    );
+  };
+
+  MultipleSelection.prototype.clear = function () {
+    this.$selection.find('.select2-selection__rendered').empty();
+  };
+
+  MultipleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  MultipleSelection.prototype.selectionContainer = function () {
+    var $container = $(
+      '<li class="select2-selection__choice">' +
+        '<span class="select2-selection__choice__remove" role="presentation">' +
+          '&times;' +
+        '</span>' +
+      '</li>'
+    );
+
+    return $container;
+  };
+
+  MultipleSelection.prototype.update = function (data) {
+    this.clear();
+
+    if (data.length === 0) {
+      return;
+    }
+
+    var $selections = [];
+
+    for (var d = 0; d < data.length; d++) {
+      var selection = data[d];
+
+      var $selection = this.selectionContainer();
+      var formatted = this.display(selection, $selection);
+
+      $selection.append(formatted);
+      $selection.prop('title', selection.title || selection.text);
+
+      $selection.data('data', selection);
+
+      $selections.push($selection);
+    }
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+
+    Utils.appendMany($rendered, $selections);
+  };
+
+  return MultipleSelection;
+});
+
+S2.define('select2/selection/placeholder',[
+  '../utils'
+], function (Utils) {
+  function Placeholder (decorated, $element, options) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options);
+  }
+
+  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
+    var $placeholder = this.selectionContainer();
+
+    $placeholder.html(this.display(placeholder));
+    $placeholder.addClass('select2-selection__placeholder')
+                .removeClass('select2-selection__choice');
+
+    return $placeholder;
+  };
+
+  Placeholder.prototype.update = function (decorated, data) {
+    var singlePlaceholder = (
+      data.length == 1 && data[0].id != this.placeholder.id
+    );
+    var multipleSelections = data.length > 1;
+
+    if (multipleSelections || singlePlaceholder) {
+      return decorated.call(this, data);
+    }
+
+    this.clear();
+
+    var $placeholder = this.createPlaceholder(this.placeholder);
+
+    this.$selection.find('.select2-selection__rendered').append($placeholder);
+  };
+
+  return Placeholder;
+});
+
+S2.define('select2/selection/allowClear',[
+  'jquery',
+  '../keys'
+], function ($, KEYS) {
+  function AllowClear () { }
+
+  AllowClear.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    if (this.placeholder == null) {
+      if (this.options.get('debug') && window.console && console.error) {
+        console.error(
+          'Select2: The `allowClear` option should be used in combination ' +
+          'with the `placeholder` option.'
+        );
+      }
+    }
+
+    this.$selection.on('mousedown', '.select2-selection__clear',
+      function (evt) {
+        self._handleClear(evt);
+    });
+
+    container.on('keypress', function (evt) {
+      self._handleKeyboardClear(evt, container);
+    });
+  };
+
+  AllowClear.prototype._handleClear = function (_, evt) {
+    // Ignore the event if it is disabled
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    var $clear = this.$selection.find('.select2-selection__clear');
+
+    // Ignore the event if nothing has been selected
+    if ($clear.length === 0) {
+      return;
+    }
+
+    evt.stopPropagation();
+
+    var data = $clear.data('data');
+
+    for (var d = 0; d < data.length; d++) {
+      var unselectData = {
+        data: data[d]
+      };
+
+      // Trigger the `unselect` event, so people can prevent it from being
+      // cleared.
+      this.trigger('unselect', unselectData);
+
+      // If the event was prevented, don't clear it out.
+      if (unselectData.prevented) {
+        return;
+      }
+    }
+
+    this.$element.val(this.placeholder.id).trigger('change');
+
+    this.trigger('toggle', {});
+  };
+
+  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
+    if (container.isOpen()) {
+      return;
+    }
+
+    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
+      this._handleClear(evt);
+    }
+  };
+
+  AllowClear.prototype.update = function (decorated, data) {
+    decorated.call(this, data);
+
+    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
+        data.length === 0) {
+      return;
+    }
+
+    var $remove = $(
+      '<span class="select2-selection__clear">' +
+        '&times;' +
+      '</span>'
+    );
+    $remove.data('data', data);
+
+    this.$selection.find('.select2-selection__rendered').prepend($remove);
+  };
+
+  return AllowClear;
+});
+
+S2.define('select2/selection/search',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function Search (decorated, $element, options) {
+    decorated.call(this, $element, options);
+  }
+
+  Search.prototype.render = function (decorated) {
+    var $search = $(
+      '<li class="select2-search select2-search--inline">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
+      '</li>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    var $rendered = decorated.call(this);
+
+    this._transferTabIndex();
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self.$search.trigger('focus');
+    });
+
+    container.on('close', function () {
+      self.$search.val('');
+      self.$search.removeAttr('aria-activedescendant');
+      self.$search.trigger('focus');
+    });
+
+    container.on('enable', function () {
+      self.$search.prop('disabled', false);
+
+      self._transferTabIndex();
+    });
+
+    container.on('disable', function () {
+      self.$search.prop('disabled', true);
+    });
+
+    container.on('focus', function (evt) {
+      self.$search.trigger('focus');
+    });
+
+    container.on('results:focus', function (params) {
+      self.$search.attr('aria-activedescendant', params.id);
+    });
+
+    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
+      evt.stopPropagation();
+
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+
+      var key = evt.which;
+
+      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
+        var $previousChoice = self.$searchContainer
+          .prev('.select2-selection__choice');
+
+        if ($previousChoice.length > 0) {
+          var item = $previousChoice.data('data');
+
+          self.searchRemoveChoice(item);
+
+          evt.preventDefault();
+        }
+      }
+    });
+
+    // Try to detect the IE version should the `documentMode` property that
+    // is stored on the document. This is only implemented in IE and is
+    // slightly cleaner than doing a user agent check.
+    // This property is not available in Edge, but Edge also doesn't have
+    // this bug.
+    var msie = document.documentMode;
+    var disableInputEvents = msie && msie <= 11;
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$selection.on(
+      'input.searchcheck',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents) {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        // Unbind the duplicated `keyup` event
+        self.$selection.off('keyup.search');
+      }
+    );
+
+    this.$selection.on(
+      'keyup.search input.search',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents && evt.type === 'input') {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        var key = evt.which;
+
+        // We can freely ignore events from modifier keys
+        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
+          return;
+        }
+
+        // Tabbing will be handled during the `keydown` phase
+        if (key == KEYS.TAB) {
+          return;
+        }
+
+        self.handleSearch(evt);
+      }
+    );
+  };
+
+  /**
+   * This method will transfer the tabindex attribute from the rendered
+   * selection to the search box. This allows for the search box to be used as
+   * the primary focus instead of the selection container.
+   *
+   * @private
+   */
+  Search.prototype._transferTabIndex = function (decorated) {
+    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
+    this.$selection.attr('tabindex', '-1');
+  };
+
+  Search.prototype.createPlaceholder = function (decorated, placeholder) {
+    this.$search.attr('placeholder', placeholder.text);
+  };
+
+  Search.prototype.update = function (decorated, data) {
+    var searchHadFocus = this.$search[0] == document.activeElement;
+
+    this.$search.attr('placeholder', '');
+
+    decorated.call(this, data);
+
+    this.$selection.find('.select2-selection__rendered')
+                   .append(this.$searchContainer);
+
+    this.resizeSearch();
+    if (searchHadFocus) {
+      this.$search.focus();
+    }
+  };
+
+  Search.prototype.handleSearch = function () {
+    this.resizeSearch();
+
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.searchRemoveChoice = function (decorated, item) {
+    this.trigger('unselect', {
+      data: item
+    });
+
+    this.$search.val(item.text);
+    this.handleSearch();
+  };
+
+  Search.prototype.resizeSearch = function () {
+    this.$search.css('width', '25px');
+
+    var width = '';
+
+    if (this.$search.attr('placeholder') !== '') {
+      width = this.$selection.find('.select2-selection__rendered').innerWidth();
+    } else {
+      var minimumWidth = this.$search.val().length + 1;
+
+      width = (minimumWidth * 0.75) + 'em';
+    }
+
+    this.$search.css('width', width);
+  };
+
+  return Search;
+});
+
+S2.define('select2/selection/eventRelay',[
+  'jquery'
+], function ($) {
+  function EventRelay () { }
+
+  EventRelay.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+    var relayEvents = [
+      'open', 'opening',
+      'close', 'closing',
+      'select', 'selecting',
+      'unselect', 'unselecting'
+    ];
+
+    var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
+
+    decorated.call(this, container, $container);
+
+    container.on('*', function (name, params) {
+      // Ignore events that should not be relayed
+      if ($.inArray(name, relayEvents) === -1) {
+        return;
+      }
+
+      // The parameters should always be an object
+      params = params || {};
+
+      // Generate the jQuery event for the Select2 event
+      var evt = $.Event('select2:' + name, {
+        params: params
+      });
+
+      self.$element.trigger(evt);
+
+      // Only handle preventable events if it was one
+      if ($.inArray(name, preventableEvents) === -1) {
+        return;
+      }
+
+      params.prevented = evt.isDefaultPrevented();
+    });
+  };
+
+  return EventRelay;
+});
+
+S2.define('select2/translation',[
+  'jquery',
+  'require'
+], function ($, require) {
+  function Translation (dict) {
+    this.dict = dict || {};
+  }
+
+  Translation.prototype.all = function () {
+    return this.dict;
+  };
+
+  Translation.prototype.get = function (key) {
+    return this.dict[key];
+  };
+
+  Translation.prototype.extend = function (translation) {
+    this.dict = $.extend({}, translation.all(), this.dict);
+  };
+
+  // Static functions
+
+  Translation._cache = {};
+
+  Translation.loadPath = function (path) {
+    if (!(path in Translation._cache)) {
+      var translations = require(path);
+
+      Translation._cache[path] = translations;
+    }
+
+    return new Translation(Translation._cache[path]);
+  };
+
+  return Translation;
+});
+
+S2.define('select2/diacritics',[
+
+], function () {
+  var diacritics = {
+    '\u24B6': 'A',
+    '\uFF21': 'A',
+    '\u00C0': 'A',
+    '\u00C1': 'A',
+    '\u00C2': 'A',
+    '\u1EA6': 'A',
+    '\u1EA4': 'A',
+    '\u1EAA': 'A',
+    '\u1EA8': 'A',
+    '\u00C3': 'A',
+    '\u0100': 'A',
+    '\u0102': 'A',
+    '\u1EB0': 'A',
+    '\u1EAE': 'A',
+    '\u1EB4': 'A',
+    '\u1EB2': 'A',
+    '\u0226': 'A',
+    '\u01E0': 'A',
+    '\u00C4': 'A',
+    '\u01DE': 'A',
+    '\u1EA2': 'A',
+    '\u00C5': 'A',
+    '\u01FA': 'A',
+    '\u01CD': 'A',
+    '\u0200': 'A',
+    '\u0202': 'A',
+    '\u1EA0': 'A',
+    '\u1EAC': 'A',
+    '\u1EB6': 'A',
+    '\u1E00': 'A',
+    '\u0104': 'A',
+    '\u023A': 'A',
+    '\u2C6F': 'A',
+    '\uA732': 'AA',
+    '\u00C6': 'AE',
+    '\u01FC': 'AE',
+    '\u01E2': 'AE',
+    '\uA734': 'AO',
+    '\uA736': 'AU',
+    '\uA738': 'AV',
+    '\uA73A': 'AV',
+    '\uA73C': 'AY',
+    '\u24B7': 'B',
+    '\uFF22': 'B',
+    '\u1E02': 'B',
+    '\u1E04': 'B',
+    '\u1E06': 'B',
+    '\u0243': 'B',
+    '\u0182': 'B',
+    '\u0181': 'B',
+    '\u24B8': 'C',
+    '\uFF23': 'C',
+    '\u0106': 'C',
+    '\u0108': 'C',
+    '\u010A': 'C',
+    '\u010C': 'C',
+    '\u00C7': 'C',
+    '\u1E08': 'C',
+    '\u0187': 'C',
+    '\u023B': 'C',
+    '\uA73E': 'C',
+    '\u24B9': 'D',
+    '\uFF24': 'D',
+    '\u1E0A': 'D',
+    '\u010E': 'D',
+    '\u1E0C': 'D',
+    '\u1E10': 'D',
+    '\u1E12': 'D',
+    '\u1E0E': 'D',
+    '\u0110': 'D',
+    '\u018B': 'D',
+    '\u018A': 'D',
+    '\u0189': 'D',
+    '\uA779': 'D',
+    '\u01F1': 'DZ',
+    '\u01C4': 'DZ',
+    '\u01F2': 'Dz',
+    '\u01C5': 'Dz',
+    '\u24BA': 'E',
+    '\uFF25': 'E',
+    '\u00C8': 'E',
+    '\u00C9': 'E',
+    '\u00CA': 'E',
+    '\u1EC0': 'E',
+    '\u1EBE': 'E',
+    '\u1EC4': 'E',
+    '\u1EC2': 'E',
+    '\u1EBC': 'E',
+    '\u0112': 'E',
+    '\u1E14': 'E',
+    '\u1E16': 'E',
+    '\u0114': 'E',
+    '\u0116': 'E',
+    '\u00CB': 'E',
+    '\u1EBA': 'E',
+    '\u011A': 'E',
+    '\u0204': 'E',
+    '\u0206': 'E',
+    '\u1EB8': 'E',
+    '\u1EC6': 'E',
+    '\u0228': 'E',
+    '\u1E1C': 'E',
+    '\u0118': 'E',
+    '\u1E18': 'E',
+    '\u1E1A': 'E',
+    '\u0190': 'E',
+    '\u018E': 'E',
+    '\u24BB': 'F',
+    '\uFF26': 'F',
+    '\u1E1E': 'F',
+    '\u0191': 'F',
+    '\uA77B': 'F',
+    '\u24BC': 'G',
+    '\uFF27': 'G',
+    '\u01F4': 'G',
+    '\u011C': 'G',
+    '\u1E20': 'G',
+    '\u011E': 'G',
+    '\u0120': 'G',
+    '\u01E6': 'G',
+    '\u0122': 'G',
+    '\u01E4': 'G',
+    '\u0193': 'G',
+    '\uA7A0': 'G',
+    '\uA77D': 'G',
+    '\uA77E': 'G',
+    '\u24BD': 'H',
+    '\uFF28': 'H',
+    '\u0124': 'H',
+    '\u1E22': 'H',
+    '\u1E26': 'H',
+    '\u021E': 'H',
+    '\u1E24': 'H',
+    '\u1E28': 'H',
+    '\u1E2A': 'H',
+    '\u0126': 'H',
+    '\u2C67': 'H',
+    '\u2C75': 'H',
+    '\uA78D': 'H',
+    '\u24BE': 'I',
+    '\uFF29': 'I',
+    '\u00CC': 'I',
+    '\u00CD': 'I',
+    '\u00CE': 'I',
+    '\u0128': 'I',
+    '\u012A': 'I',
+    '\u012C': 'I',
+    '\u0130': 'I',
+    '\u00CF': 'I',
+    '\u1E2E': 'I',
+    '\u1EC8': 'I',
+    '\u01CF': 'I',
+    '\u0208': 'I',
+    '\u020A': 'I',
+    '\u1ECA': 'I',
+    '\u012E': 'I',
+    '\u1E2C': 'I',
+    '\u0197': 'I',
+    '\u24BF': 'J',
+    '\uFF2A': 'J',
+    '\u0134': 'J',
+    '\u0248': 'J',
+    '\u24C0': 'K',
+    '\uFF2B': 'K',
+    '\u1E30': 'K',
+    '\u01E8': 'K',
+    '\u1E32': 'K',
+    '\u0136': 'K',
+    '\u1E34': 'K',
+    '\u0198': 'K',
+    '\u2C69': 'K',
+    '\uA740': 'K',
+    '\uA742': 'K',
+    '\uA744': 'K',
+    '\uA7A2': 'K',
+    '\u24C1': 'L',
+    '\uFF2C': 'L',
+    '\u013F': 'L',
+    '\u0139': 'L',
+    '\u013D': 'L',
+    '\u1E36': 'L',
+    '\u1E38': 'L',
+    '\u013B': 'L',
+    '\u1E3C': 'L',
+    '\u1E3A': 'L',
+    '\u0141': 'L',
+    '\u023D': 'L',
+    '\u2C62': 'L',
+    '\u2C60': 'L',
+    '\uA748': 'L',
+    '\uA746': 'L',
+    '\uA780': 'L',
+    '\u01C7': 'LJ',
+    '\u01C8': 'Lj',
+    '\u24C2': 'M',
+    '\uFF2D': 'M',
+    '\u1E3E': 'M',
+    '\u1E40': 'M',
+    '\u1E42': 'M',
+    '\u2C6E': 'M',
+    '\u019C': 'M',
+    '\u24C3': 'N',
+    '\uFF2E': 'N',
+    '\u01F8': 'N',
+    '\u0143': 'N',
+    '\u00D1': 'N',
+    '\u1E44': 'N',
+    '\u0147': 'N',
+    '\u1E46': 'N',
+    '\u0145': 'N',
+    '\u1E4A': 'N',
+    '\u1E48': 'N',
+    '\u0220': 'N',
+    '\u019D': 'N',
+    '\uA790': 'N',
+    '\uA7A4': 'N',
+    '\u01CA': 'NJ',
+    '\u01CB': 'Nj',
+    '\u24C4': 'O',
+    '\uFF2F': 'O',
+    '\u00D2': 'O',
+    '\u00D3': 'O',
+    '\u00D4': 'O',
+    '\u1ED2': 'O',
+    '\u1ED0': 'O',
+    '\u1ED6': 'O',
+    '\u1ED4': 'O',
+    '\u00D5': 'O',
+    '\u1E4C': 'O',
+    '\u022C': 'O',
+    '\u1E4E': 'O',
+    '\u014C': 'O',
+    '\u1E50': 'O',
+    '\u1E52': 'O',
+    '\u014E': 'O',
+    '\u022E': 'O',
+    '\u0230': 'O',
+    '\u00D6': 'O',
+    '\u022A': 'O',
+    '\u1ECE': 'O',
+    '\u0150': 'O',
+    '\u01D1': 'O',
+    '\u020C': 'O',
+    '\u020E': 'O',
+    '\u01A0': 'O',
+    '\u1EDC': 'O',
+    '\u1EDA': 'O',
+    '\u1EE0': 'O',
+    '\u1EDE': 'O',
+    '\u1EE2': 'O',
+    '\u1ECC': 'O',
+    '\u1ED8': 'O',
+    '\u01EA': 'O',
+    '\u01EC': 'O',
+    '\u00D8': 'O',
+    '\u01FE': 'O',
+    '\u0186': 'O',
+    '\u019F': 'O',
+    '\uA74A': 'O',
+    '\uA74C': 'O',
+    '\u01A2': 'OI',
+    '\uA74E': 'OO',
+    '\u0222': 'OU',
+    '\u24C5': 'P',
+    '\uFF30': 'P',
+    '\u1E54': 'P',
+    '\u1E56': 'P',
+    '\u01A4': 'P',
+    '\u2C63': 'P',
+    '\uA750': 'P',
+    '\uA752': 'P',
+    '\uA754': 'P',
+    '\u24C6': 'Q',
+    '\uFF31': 'Q',
+    '\uA756': 'Q',
+    '\uA758': 'Q',
+    '\u024A': 'Q',
+    '\u24C7': 'R',
+    '\uFF32': 'R',
+    '\u0154': 'R',
+    '\u1E58': 'R',
+    '\u0158': 'R',
+    '\u0210': 'R',
+    '\u0212': 'R',
+    '\u1E5A': 'R',
+    '\u1E5C': 'R',
+    '\u0156': 'R',
+    '\u1E5E': 'R',
+    '\u024C': 'R',
+    '\u2C64': 'R',
+    '\uA75A': 'R',
+    '\uA7A6': 'R',
+    '\uA782': 'R',
+    '\u24C8': 'S',
+    '\uFF33': 'S',
+    '\u1E9E': 'S',
+    '\u015A': 'S',
+    '\u1E64': 'S',
+    '\u015C': 'S',
+    '\u1E60': 'S',
+    '\u0160': 'S',
+    '\u1E66': 'S',
+    '\u1E62': 'S',
+    '\u1E68': 'S',
+    '\u0218': 'S',
+    '\u015E': 'S',
+    '\u2C7E': 'S',
+    '\uA7A8': 'S',
+    '\uA784': 'S',
+    '\u24C9': 'T',
+    '\uFF34': 'T',
+    '\u1E6A': 'T',
+    '\u0164': 'T',
+    '\u1E6C': 'T',
+    '\u021A': 'T',
+    '\u0162': 'T',
+    '\u1E70': 'T',
+    '\u1E6E': 'T',
+    '\u0166': 'T',
+    '\u01AC': 'T',
+    '\u01AE': 'T',
+    '\u023E': 'T',
+    '\uA786': 'T',
+    '\uA728': 'TZ',
+    '\u24CA': 'U',
+    '\uFF35': 'U',
+    '\u00D9': 'U',
+    '\u00DA': 'U',
+    '\u00DB': 'U',
+    '\u0168': 'U',
+    '\u1E78': 'U',
+    '\u016A': 'U',
+    '\u1E7A': 'U',
+    '\u016C': 'U',
+    '\u00DC': 'U',
+    '\u01DB': 'U',
+    '\u01D7': 'U',
+    '\u01D5': 'U',
+    '\u01D9': 'U',
+    '\u1EE6': 'U',
+    '\u016E': 'U',
+    '\u0170': 'U',
+    '\u01D3': 'U',
+    '\u0214': 'U',
+    '\u0216': 'U',
+    '\u01AF': 'U',
+    '\u1EEA': 'U',
+    '\u1EE8': 'U',
+    '\u1EEE': 'U',
+    '\u1EEC': 'U',
+    '\u1EF0': 'U',
+    '\u1EE4': 'U',
+    '\u1E72': 'U',
+    '\u0172': 'U',
+    '\u1E76': 'U',
+    '\u1E74': 'U',
+    '\u0244': 'U',
+    '\u24CB': 'V',
+    '\uFF36': 'V',
+    '\u1E7C': 'V',
+    '\u1E7E': 'V',
+    '\u01B2': 'V',
+    '\uA75E': 'V',
+    '\u0245': 'V',
+    '\uA760': 'VY',
+    '\u24CC': 'W',
+    '\uFF37': 'W',
+    '\u1E80': 'W',
+    '\u1E82': 'W',
+    '\u0174': 'W',
+    '\u1E86': 'W',
+    '\u1E84': 'W',
+    '\u1E88': 'W',
+    '\u2C72': 'W',
+    '\u24CD': 'X',
+    '\uFF38': 'X',
+    '\u1E8A': 'X',
+    '\u1E8C': 'X',
+    '\u24CE': 'Y',
+    '\uFF39': 'Y',
+    '\u1EF2': 'Y',
+    '\u00DD': 'Y',
+    '\u0176': 'Y',
+    '\u1EF8': 'Y',
+    '\u0232': 'Y',
+    '\u1E8E': 'Y',
+    '\u0178': 'Y',
+    '\u1EF6': 'Y',
+    '\u1EF4': 'Y',
+    '\u01B3': 'Y',
+    '\u024E': 'Y',
+    '\u1EFE': 'Y',
+    '\u24CF': 'Z',
+    '\uFF3A': 'Z',
+    '\u0179': 'Z',
+    '\u1E90': 'Z',
+    '\u017B': 'Z',
+    '\u017D': 'Z',
+    '\u1E92': 'Z',
+    '\u1E94': 'Z',
+    '\u01B5': 'Z',
+    '\u0224': 'Z',
+    '\u2C7F': 'Z',
+    '\u2C6B': 'Z',
+    '\uA762': 'Z',
+    '\u24D0': 'a',
+    '\uFF41': 'a',
+    '\u1E9A': 'a',
+    '\u00E0': 'a',
+    '\u00E1': 'a',
+    '\u00E2': 'a',
+    '\u1EA7': 'a',
+    '\u1EA5': 'a',
+    '\u1EAB': 'a',
+    '\u1EA9': 'a',
+    '\u00E3': 'a',
+    '\u0101': 'a',
+    '\u0103': 'a',
+    '\u1EB1': 'a',
+    '\u1EAF': 'a',
+    '\u1EB5': 'a',
+    '\u1EB3': 'a',
+    '\u0227': 'a',
+    '\u01E1': 'a',
+    '\u00E4': 'a',
+    '\u01DF': 'a',
+    '\u1EA3': 'a',
+    '\u00E5': 'a',
+    '\u01FB': 'a',
+    '\u01CE': 'a',
+    '\u0201': 'a',
+    '\u0203': 'a',
+    '\u1EA1': 'a',
+    '\u1EAD': 'a',
+    '\u1EB7': 'a',
+    '\u1E01': 'a',
+    '\u0105': 'a',
+    '\u2C65': 'a',
+    '\u0250': 'a',
+    '\uA733': 'aa',
+    '\u00E6': 'ae',
+    '\u01FD': 'ae',
+    '\u01E3': 'ae',
+    '\uA735': 'ao',
+    '\uA737': 'au',
+    '\uA739': 'av',
+    '\uA73B': 'av',
+    '\uA73D': 'ay',
+    '\u24D1': 'b',
+    '\uFF42': 'b',
+    '\u1E03': 'b',
+    '\u1E05': 'b',
+    '\u1E07': 'b',
+    '\u0180': 'b',
+    '\u0183': 'b',
+    '\u0253': 'b',
+    '\u24D2': 'c',
+    '\uFF43': 'c',
+    '\u0107': 'c',
+    '\u0109': 'c',
+    '\u010B': 'c',
+    '\u010D': 'c',
+    '\u00E7': 'c',
+    '\u1E09': 'c',
+    '\u0188': 'c',
+    '\u023C': 'c',
+    '\uA73F': 'c',
+    '\u2184': 'c',
+    '\u24D3': 'd',
+    '\uFF44': 'd',
+    '\u1E0B': 'd',
+    '\u010F': 'd',
+    '\u1E0D': 'd',
+    '\u1E11': 'd',
+    '\u1E13': 'd',
+    '\u1E0F': 'd',
+    '\u0111': 'd',
+    '\u018C': 'd',
+    '\u0256': 'd',
+    '\u0257': 'd',
+    '\uA77A': 'd',
+    '\u01F3': 'dz',
+    '\u01C6': 'dz',
+    '\u24D4': 'e',
+    '\uFF45': 'e',
+    '\u00E8': 'e',
+    '\u00E9': 'e',
+    '\u00EA': 'e',
+    '\u1EC1': 'e',
+    '\u1EBF': 'e',
+    '\u1EC5': 'e',
+    '\u1EC3': 'e',
+    '\u1EBD': 'e',
+    '\u0113': 'e',
+    '\u1E15': 'e',
+    '\u1E17': 'e',
+    '\u0115': 'e',
+    '\u0117': 'e',
+    '\u00EB': 'e',
+    '\u1EBB': 'e',
+    '\u011B': 'e',
+    '\u0205': 'e',
+    '\u0207': 'e',
+    '\u1EB9': 'e',
+    '\u1EC7': 'e',
+    '\u0229': 'e',
+    '\u1E1D': 'e',
+    '\u0119': 'e',
+    '\u1E19': 'e',
+    '\u1E1B': 'e',
+    '\u0247': 'e',
+    '\u025B': 'e',
+    '\u01DD': 'e',
+    '\u24D5': 'f',
+    '\uFF46': 'f',
+    '\u1E1F': 'f',
+    '\u0192': 'f',
+    '\uA77C': 'f',
+    '\u24D6': 'g',
+    '\uFF47': 'g',
+    '\u01F5': 'g',
+    '\u011D': 'g',
+    '\u1E21': 'g',
+    '\u011F': 'g',
+    '\u0121': 'g',
+    '\u01E7': 'g',
+    '\u0123': 'g',
+    '\u01E5': 'g',
+    '\u0260': 'g',
+    '\uA7A1': 'g',
+    '\u1D79': 'g',
+    '\uA77F': 'g',
+    '\u24D7': 'h',
+    '\uFF48': 'h',
+    '\u0125': 'h',
+    '\u1E23': 'h',
+    '\u1E27': 'h',
+    '\u021F': 'h',
+    '\u1E25': 'h',
+    '\u1E29': 'h',
+    '\u1E2B': 'h',
+    '\u1E96': 'h',
+    '\u0127': 'h',
+    '\u2C68': 'h',
+    '\u2C76': 'h',
+    '\u0265': 'h',
+    '\u0195': 'hv',
+    '\u24D8': 'i',
+    '\uFF49': 'i',
+    '\u00EC': 'i',
+    '\u00ED': 'i',
+    '\u00EE': 'i',
+    '\u0129': 'i',
+    '\u012B': 'i',
+    '\u012D': 'i',
+    '\u00EF': 'i',
+    '\u1E2F': 'i',
+    '\u1EC9': 'i',
+    '\u01D0': 'i',
+    '\u0209': 'i',
+    '\u020B': 'i',
+    '\u1ECB': 'i',
+    '\u012F': 'i',
+    '\u1E2D': 'i',
+    '\u0268': 'i',
+    '\u0131': 'i',
+    '\u24D9': 'j',
+    '\uFF4A': 'j',
+    '\u0135': 'j',
+    '\u01F0': 'j',
+    '\u0249': 'j',
+    '\u24DA': 'k',
+    '\uFF4B': 'k',
+    '\u1E31': 'k',
+    '\u01E9': 'k',
+    '\u1E33': 'k',
+    '\u0137': 'k',
+    '\u1E35': 'k',
+    '\u0199': 'k',
+    '\u2C6A': 'k',
+    '\uA741': 'k',
+    '\uA743': 'k',
+    '\uA745': 'k',
+    '\uA7A3': 'k',
+    '\u24DB': 'l',
+    '\uFF4C': 'l',
+    '\u0140': 'l',
+    '\u013A': 'l',
+    '\u013E': 'l',
+    '\u1E37': 'l',
+    '\u1E39': 'l',
+    '\u013C': 'l',
+    '\u1E3D': 'l',
+    '\u1E3B': 'l',
+    '\u017F': 'l',
+    '\u0142': 'l',
+    '\u019A': 'l',
+    '\u026B': 'l',
+    '\u2C61': 'l',
+    '\uA749': 'l',
+    '\uA781': 'l',
+    '\uA747': 'l',
+    '\u01C9': 'lj',
+    '\u24DC': 'm',
+    '\uFF4D': 'm',
+    '\u1E3F': 'm',
+    '\u1E41': 'm',
+    '\u1E43': 'm',
+    '\u0271': 'm',
+    '\u026F': 'm',
+    '\u24DD': 'n',
+    '\uFF4E': 'n',
+    '\u01F9': 'n',
+    '\u0144': 'n',
+    '\u00F1': 'n',
+    '\u1E45': 'n',
+    '\u0148': 'n',
+    '\u1E47': 'n',
+    '\u0146': 'n',
+    '\u1E4B': 'n',
+    '\u1E49': 'n',
+    '\u019E': 'n',
+    '\u0272': 'n',
+    '\u0149': 'n',
+    '\uA791': 'n',
+    '\uA7A5': 'n',
+    '\u01CC': 'nj',
+    '\u24DE': 'o',
+    '\uFF4F': 'o',
+    '\u00F2': 'o',
+    '\u00F3': 'o',
+    '\u00F4': 'o',
+    '\u1ED3': 'o',
+    '\u1ED1': 'o',
+    '\u1ED7': 'o',
+    '\u1ED5': 'o',
+    '\u00F5': 'o',
+    '\u1E4D': 'o',
+    '\u022D': 'o',
+    '\u1E4F': 'o',
+    '\u014D': 'o',
+    '\u1E51': 'o',
+    '\u1E53': 'o',
+    '\u014F': 'o',
+    '\u022F': 'o',
+    '\u0231': 'o',
+    '\u00F6': 'o',
+    '\u022B': 'o',
+    '\u1ECF': 'o',
+    '\u0151': 'o',
+    '\u01D2': 'o',
+    '\u020D': 'o',
+    '\u020F': 'o',
+    '\u01A1': 'o',
+    '\u1EDD': 'o',
+    '\u1EDB': 'o',
+    '\u1EE1': 'o',
+    '\u1EDF': 'o',
+    '\u1EE3': 'o',
+    '\u1ECD': 'o',
+    '\u1ED9': 'o',
+    '\u01EB': 'o',
+    '\u01ED': 'o',
+    '\u00F8': 'o',
+    '\u01FF': 'o',
+    '\u0254': 'o',
+    '\uA74B': 'o',
+    '\uA74D': 'o',
+    '\u0275': 'o',
+    '\u01A3': 'oi',
+    '\u0223': 'ou',
+    '\uA74F': 'oo',
+    '\u24DF': 'p',
+    '\uFF50': 'p',
+    '\u1E55': 'p',
+    '\u1E57': 'p',
+    '\u01A5': 'p',
+    '\u1D7D': 'p',
+    '\uA751': 'p',
+    '\uA753': 'p',
+    '\uA755': 'p',
+    '\u24E0': 'q',
+    '\uFF51': 'q',
+    '\u024B': 'q',
+    '\uA757': 'q',
+    '\uA759': 'q',
+    '\u24E1': 'r',
+    '\uFF52': 'r',
+    '\u0155': 'r',
+    '\u1E59': 'r',
+    '\u0159': 'r',
+    '\u0211': 'r',
+    '\u0213': 'r',
+    '\u1E5B': 'r',
+    '\u1E5D': 'r',
+    '\u0157': 'r',
+    '\u1E5F': 'r',
+    '\u024D': 'r',
+    '\u027D': 'r',
+    '\uA75B': 'r',
+    '\uA7A7': 'r',
+    '\uA783': 'r',
+    '\u24E2': 's',
+    '\uFF53': 's',
+    '\u00DF': 's',
+    '\u015B': 's',
+    '\u1E65': 's',
+    '\u015D': 's',
+    '\u1E61': 's',
+    '\u0161': 's',
+    '\u1E67': 's',
+    '\u1E63': 's',
+    '\u1E69': 's',
+    '\u0219': 's',
+    '\u015F': 's',
+    '\u023F': 's',
+    '\uA7A9': 's',
+    '\uA785': 's',
+    '\u1E9B': 's',
+    '\u24E3': 't',
+    '\uFF54': 't',
+    '\u1E6B': 't',
+    '\u1E97': 't',
+    '\u0165': 't',
+    '\u1E6D': 't',
+    '\u021B': 't',
+    '\u0163': 't',
+    '\u1E71': 't',
+    '\u1E6F': 't',
+    '\u0167': 't',
+    '\u01AD': 't',
+    '\u0288': 't',
+    '\u2C66': 't',
+    '\uA787': 't',
+    '\uA729': 'tz',
+    '\u24E4': 'u',
+    '\uFF55': 'u',
+    '\u00F9': 'u',
+    '\u00FA': 'u',
+    '\u00FB': 'u',
+    '\u0169': 'u',
+    '\u1E79': 'u',
+    '\u016B': 'u',
+    '\u1E7B': 'u',
+    '\u016D': 'u',
+    '\u00FC': 'u',
+    '\u01DC': 'u',
+    '\u01D8': 'u',
+    '\u01D6': 'u',
+    '\u01DA': 'u',
+    '\u1EE7': 'u',
+    '\u016F': 'u',
+    '\u0171': 'u',
+    '\u01D4': 'u',
+    '\u0215': 'u',
+    '\u0217': 'u',
+    '\u01B0': 'u',
+    '\u1EEB': 'u',
+    '\u1EE9': 'u',
+    '\u1EEF': 'u',
+    '\u1EED': 'u',
+    '\u1EF1': 'u',
+    '\u1EE5': 'u',
+    '\u1E73': 'u',
+    '\u0173': 'u',
+    '\u1E77': 'u',
+    '\u1E75': 'u',
+    '\u0289': 'u',
+    '\u24E5': 'v',
+    '\uFF56': 'v',
+    '\u1E7D': 'v',
+    '\u1E7F': 'v',
+    '\u028B': 'v',
+    '\uA75F': 'v',
+    '\u028C': 'v',
+    '\uA761': 'vy',
+    '\u24E6': 'w',
+    '\uFF57': 'w',
+    '\u1E81': 'w',
+    '\u1E83': 'w',
+    '\u0175': 'w',
+    '\u1E87': 'w',
+    '\u1E85': 'w',
+    '\u1E98': 'w',
+    '\u1E89': 'w',
+    '\u2C73': 'w',
+    '\u24E7': 'x',
+    '\uFF58': 'x',
+    '\u1E8B': 'x',
+    '\u1E8D': 'x',
+    '\u24E8': 'y',
+    '\uFF59': 'y',
+    '\u1EF3': 'y',
+    '\u00FD': 'y',
+    '\u0177': 'y',
+    '\u1EF9': 'y',
+    '\u0233': 'y',
+    '\u1E8F': 'y',
+    '\u00FF': 'y',
+    '\u1EF7': 'y',
+    '\u1E99': 'y',
+    '\u1EF5': 'y',
+    '\u01B4': 'y',
+    '\u024F': 'y',
+    '\u1EFF': 'y',
+    '\u24E9': 'z',
+    '\uFF5A': 'z',
+    '\u017A': 'z',
+    '\u1E91': 'z',
+    '\u017C': 'z',
+    '\u017E': 'z',
+    '\u1E93': 'z',
+    '\u1E95': 'z',
+    '\u01B6': 'z',
+    '\u0225': 'z',
+    '\u0240': 'z',
+    '\u2C6C': 'z',
+    '\uA763': 'z',
+    '\u0386': '\u0391',
+    '\u0388': '\u0395',
+    '\u0389': '\u0397',
+    '\u038A': '\u0399',
+    '\u03AA': '\u0399',
+    '\u038C': '\u039F',
+    '\u038E': '\u03A5',
+    '\u03AB': '\u03A5',
+    '\u038F': '\u03A9',
+    '\u03AC': '\u03B1',
+    '\u03AD': '\u03B5',
+    '\u03AE': '\u03B7',
+    '\u03AF': '\u03B9',
+    '\u03CA': '\u03B9',
+    '\u0390': '\u03B9',
+    '\u03CC': '\u03BF',
+    '\u03CD': '\u03C5',
+    '\u03CB': '\u03C5',
+    '\u03B0': '\u03C5',
+    '\u03C9': '\u03C9',
+    '\u03C2': '\u03C3'
+  };
+
+  return diacritics;
+});
+
+S2.define('select2/data/base',[
+  '../utils'
+], function (Utils) {
+  function BaseAdapter ($element, options) {
+    BaseAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseAdapter, Utils.Observable);
+
+  BaseAdapter.prototype.current = function (callback) {
+    throw new Error('The `current` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.query = function (params, callback) {
+    throw new Error('The `query` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.bind = function (container, $container) {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.destroy = function () {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.generateResultId = function (container, data) {
+    var id = container.id + '-result-';
+
+    id += Utils.generateChars(4);
+
+    if (data.id != null) {
+      id += '-' + data.id.toString();
+    } else {
+      id += '-' + Utils.generateChars(4);
+    }
+    return id;
+  };
+
+  return BaseAdapter;
+});
+
+S2.define('select2/data/select',[
+  './base',
+  '../utils',
+  'jquery'
+], function (BaseAdapter, Utils, $) {
+  function SelectAdapter ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    SelectAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(SelectAdapter, BaseAdapter);
+
+  SelectAdapter.prototype.current = function (callback) {
+    var data = [];
+    var self = this;
+
+    this.$element.find(':selected').each(function () {
+      var $option = $(this);
+
+      var option = self.item($option);
+
+      data.push(option);
+    });
+
+    callback(data);
+  };
+
+  SelectAdapter.prototype.select = function (data) {
+    var self = this;
+
+    data.selected = true;
+
+    // If data.element is a DOM node, use it instead
+    if ($(data.element).is('option')) {
+      data.element.selected = true;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    if (this.$element.prop('multiple')) {
+      this.current(function (currentData) {
+        var val = [];
+
+        data = [data];
+        data.push.apply(data, currentData);
+
+        for (var d = 0; d < data.length; d++) {
+          var id = data[d].id;
+
+          if ($.inArray(id, val) === -1) {
+            val.push(id);
+          }
+        }
+
+        self.$element.val(val);
+        self.$element.trigger('change');
+      });
+    } else {
+      var val = data.id;
+
+      this.$element.val(val);
+      this.$element.trigger('change');
+    }
+  };
+
+  SelectAdapter.prototype.unselect = function (data) {
+    var self = this;
+
+    if (!this.$element.prop('multiple')) {
+      return;
+    }
+
+    data.selected = false;
+
+    if ($(data.element).is('option')) {
+      data.element.selected = false;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    this.current(function (currentData) {
+      var val = [];
+
+      for (var d = 0; d < currentData.length; d++) {
+        var id = currentData[d].id;
+
+        if (id !== data.id && $.inArray(id, val) === -1) {
+          val.push(id);
+        }
+      }
+
+      self.$element.val(val);
+
+      self.$element.trigger('change');
+    });
+  };
+
+  SelectAdapter.prototype.bind = function (container, $container) {
+    var self = this;
+
+    this.container = container;
+
+    container.on('select', function (params) {
+      self.select(params.data);
+    });
+
+    container.on('unselect', function (params) {
+      self.unselect(params.data);
+    });
+  };
+
+  SelectAdapter.prototype.destroy = function () {
+    // Remove anything added to child elements
+    this.$element.find('*').each(function () {
+      // Remove any custom data set by Select2
+      $.removeData(this, 'data');
+    });
+  };
+
+  SelectAdapter.prototype.query = function (params, callback) {
+    var data = [];
+    var self = this;
+
+    var $options = this.$element.children();
+
+    $options.each(function () {
+      var $option = $(this);
+
+      if (!$option.is('option') && !$option.is('optgroup')) {
+        return;
+      }
+
+      var option = self.item($option);
+
+      var matches = self.matches(params, option);
+
+      if (matches !== null) {
+        data.push(matches);
+      }
+    });
+
+    callback({
+      results: data
+    });
+  };
+
+  SelectAdapter.prototype.addOptions = function ($options) {
+    Utils.appendMany(this.$element, $options);
+  };
+
+  SelectAdapter.prototype.option = function (data) {
+    var option;
+
+    if (data.children) {
+      option = document.createElement('optgroup');
+      option.label = data.text;
+    } else {
+      option = document.createElement('option');
+
+      if (option.textContent !== undefined) {
+        option.textContent = data.text;
+      } else {
+        option.innerText = data.text;
+      }
+    }
+
+    if (data.id !== undefined) {
+      option.value = data.id;
+    }
+
+    if (data.disabled) {
+      option.disabled = true;
+    }
+
+    if (data.selected) {
+      option.selected = true;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    var $option = $(option);
+
+    var normalizedData = this._normalizeItem(data);
+    normalizedData.element = option;
+
+    // Override the option's data with the combined data
+    $.data(option, 'data', normalizedData);
+
+    return $option;
+  };
+
+  SelectAdapter.prototype.item = function ($option) {
+    var data = {};
+
+    data = $.data($option[0], 'data');
+
+    if (data != null) {
+      return data;
+    }
+
+    if ($option.is('option')) {
+      data = {
+        id: $option.val(),
+        text: $option.text(),
+        disabled: $option.prop('disabled'),
+        selected: $option.prop('selected'),
+        title: $option.prop('title')
+      };
+    } else if ($option.is('optgroup')) {
+      data = {
+        text: $option.prop('label'),
+        children: [],
+        title: $option.prop('title')
+      };
+
+      var $children = $option.children('option');
+      var children = [];
+
+      for (var c = 0; c < $children.length; c++) {
+        var $child = $($children[c]);
+
+        var child = this.item($child);
+
+        children.push(child);
+      }
+
+      data.children = children;
+    }
+
+    data = this._normalizeItem(data);
+    data.element = $option[0];
+
+    $.data($option[0], 'data', data);
+
+    return data;
+  };
+
+  SelectAdapter.prototype._normalizeItem = function (item) {
+    if (!$.isPlainObject(item)) {
+      item = {
+        id: item,
+        text: item
+      };
+    }
+
+    item = $.extend({}, {
+      text: ''
+    }, item);
+
+    var defaults = {
+      selected: false,
+      disabled: false
+    };
+
+    if (item.id != null) {
+      item.id = item.id.toString();
+    }
+
+    if (item.text != null) {
+      item.text = item.text.toString();
+    }
+
+    if (item._resultId == null && item.id && this.container != null) {
+      item._resultId = this.generateResultId(this.container, item);
+    }
+
+    return $.extend({}, defaults, item);
+  };
+
+  SelectAdapter.prototype.matches = function (params, data) {
+    var matcher = this.options.get('matcher');
+
+    return matcher(params, data);
+  };
+
+  return SelectAdapter;
+});
+
+S2.define('select2/data/array',[
+  './select',
+  '../utils',
+  'jquery'
+], function (SelectAdapter, Utils, $) {
+  function ArrayAdapter ($element, options) {
+    var data = options.get('data') || [];
+
+    ArrayAdapter.__super__.constructor.call(this, $element, options);
+
+    this.addOptions(this.convertToOptions(data));
+  }
+
+  Utils.Extend(ArrayAdapter, SelectAdapter);
+
+  ArrayAdapter.prototype.select = function (data) {
+    var $option = this.$element.find('option').filter(function (i, elm) {
+      return elm.value == data.id.toString();
+    });
+
+    if ($option.length === 0) {
+      $option = this.option(data);
+
+      this.addOptions($option);
+    }
+
+    ArrayAdapter.__super__.select.call(this, data);
+  };
+
+  ArrayAdapter.prototype.convertToOptions = function (data) {
+    var self = this;
+
+    var $existing = this.$element.find('option');
+    var existingIds = $existing.map(function () {
+      return self.item($(this)).id;
+    }).get();
+
+    var $options = [];
+
+    // Filter out all items except for the one passed in the argument
+    function onlyItem (item) {
+      return function () {
+        return $(this).val() == item.id;
+      };
+    }
+
+    for (var d = 0; d < data.length; d++) {
+      var item = this._normalizeItem(data[d]);
+
+      // Skip items which were pre-loaded, only merge the data
+      if ($.inArray(item.id, existingIds) >= 0) {
+        var $existingOption = $existing.filter(onlyItem(item));
+
+        var existingData = this.item($existingOption);
+        var newData = $.extend(true, {}, item, existingData);
+
+        var $newOption = this.option(newData);
+
+        $existingOption.replaceWith($newOption);
+
+        continue;
+      }
+
+      var $option = this.option(item);
+
+      if (item.children) {
+        var $children = this.convertToOptions(item.children);
+
+        Utils.appendMany($option, $children);
+      }
+
+      $options.push($option);
+    }
+
+    return $options;
+  };
+
+  return ArrayAdapter;
+});
+
+S2.define('select2/data/ajax',[
+  './array',
+  '../utils',
+  'jquery'
+], function (ArrayAdapter, Utils, $) {
+  function AjaxAdapter ($element, options) {
+    this.ajaxOptions = this._applyDefaults(options.get('ajax'));
+
+    if (this.ajaxOptions.processResults != null) {
+      this.processResults = this.ajaxOptions.processResults;
+    }
+
+    AjaxAdapter.__super__.constructor.call(this, $element, options);
+  }
+
+  Utils.Extend(AjaxAdapter, ArrayAdapter);
+
+  AjaxAdapter.prototype._applyDefaults = function (options) {
+    var defaults = {
+      data: function (params) {
+        return $.extend({}, params, {
+          q: params.term
+        });
+      },
+      transport: function (params, success, failure) {
+        var $request = $.ajax(params);
+
+        $request.then(success);
+        $request.fail(failure);
+
+        return $request;
+      }
+    };
+
+    return $.extend({}, defaults, options, true);
+  };
+
+  AjaxAdapter.prototype.processResults = function (results) {
+    return results;
+  };
+
+  AjaxAdapter.prototype.query = function (params, callback) {
+    var matches = [];
+    var self = this;
+
+    if (this._request != null) {
+      // JSONP requests cannot always be aborted
+      if ($.isFunction(this._request.abort)) {
+        this._request.abort();
+      }
+
+      this._request = null;
+    }
+
+    var options = $.extend({
+      type: 'GET'
+    }, this.ajaxOptions);
+
+    if (typeof options.url === 'function') {
+      options.url = options.url.call(this.$element, params);
+    }
+
+    if (typeof options.data === 'function') {
+      options.data = options.data.call(this.$element, params);
+    }
+
+    function request () {
+      var $request = options.transport(options, function (data) {
+        var results = self.processResults(data, params);
+
+        if (self.options.get('debug') && window.console && console.error) {
+          // Check to make sure that the response included a `results` key.
+          if (!results || !results.results || !$.isArray(results.results)) {
+            console.error(
+              'Select2: The AJAX results did not return an array in the ' +
+              '`results` key of the response.'
+            );
+          }
+        }
+
+        callback(results);
+      }, function () {
+        // Attempt to detect if a request was aborted
+        // Only works if the transport exposes a status property
+        if ($request.status && $request.status === '0') {
+          return;
+        }
+
+        self.trigger('results:message', {
+          message: 'errorLoading'
+        });
+      });
+
+      self._request = $request;
+    }
+
+    if (this.ajaxOptions.delay && params.term != null) {
+      if (this._queryTimeout) {
+        window.clearTimeout(this._queryTimeout);
+      }
+
+      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
+    } else {
+      request();
+    }
+  };
+
+  return AjaxAdapter;
+});
+
+S2.define('select2/data/tags',[
+  'jquery'
+], function ($) {
+  function Tags (decorated, $element, options) {
+    var tags = options.get('tags');
+
+    var createTag = options.get('createTag');
+
+    if (createTag !== undefined) {
+      this.createTag = createTag;
+    }
+
+    var insertTag = options.get('insertTag');
+
+    if (insertTag !== undefined) {
+        this.insertTag = insertTag;
+    }
+
+    decorated.call(this, $element, options);
+
+    if ($.isArray(tags)) {
+      for (var t = 0; t < tags.length; t++) {
+        var tag = tags[t];
+        var item = this._normalizeItem(tag);
+
+        var $option = this.option(item);
+
+        this.$element.append($option);
+      }
+    }
+  }
+
+  Tags.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    this._removeOldTags();
+
+    if (params.term == null || params.page != null) {
+      decorated.call(this, params, callback);
+      return;
+    }
+
+    function wrapper (obj, child) {
+      var data = obj.results;
+
+      for (var i = 0; i < data.length; i++) {
+        var option = data[i];
+
+        var checkChildren = (
+          option.children != null &&
+          !wrapper({
+            results: option.children
+          }, true)
+        );
+
+        var optionText = (option.text || '').toUpperCase();
+        var paramsTerm = (params.term || '').toUpperCase();
+
+        var checkText = optionText === paramsTerm;
+
+        if (checkText || checkChildren) {
+          if (child) {
+            return false;
+          }
+
+          obj.data = data;
+          callback(obj);
+
+          return;
+        }
+      }
+
+      if (child) {
+        return true;
+      }
+
+      var tag = self.createTag(params);
+
+      if (tag != null) {
+        var $option = self.option(tag);
+        $option.attr('data-select2-tag', true);
+
+        self.addOptions([$option]);
+
+        self.insertTag(data, tag);
+      }
+
+      obj.results = data;
+
+      callback(obj);
+    }
+
+    decorated.call(this, params, wrapper);
+  };
+
+  Tags.prototype.createTag = function (decorated, params) {
+    var term = $.trim(params.term);
+
+    if (term === '') {
+      return null;
+    }
+
+    return {
+      id: term,
+      text: term
+    };
+  };
+
+  Tags.prototype.insertTag = function (_, data, tag) {
+    data.unshift(tag);
+  };
+
+  Tags.prototype._removeOldTags = function (_) {
+    var tag = this._lastTag;
+
+    var $options = this.$element.find('option[data-select2-tag]');
+
+    $options.each(function () {
+      if (this.selected) {
+        return;
+      }
+
+      $(this).remove();
+    });
+  };
+
+  return Tags;
+});
+
+S2.define('select2/data/tokenizer',[
+  'jquery'
+], function ($) {
+  function Tokenizer (decorated, $element, options) {
+    var tokenizer = options.get('tokenizer');
+
+    if (tokenizer !== undefined) {
+      this.tokenizer = tokenizer;
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  Tokenizer.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    this.$search =  container.dropdown.$search || container.selection.$search ||
+      $container.find('.select2-search__field');
+  };
+
+  Tokenizer.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    function createAndSelect (data) {
+      // Normalize the data object so we can use it for checks
+      var item = self._normalizeItem(data);
+
+      // Check if the data object already exists as a tag
+      // Select it if it doesn't
+      var $existingOptions = self.$element.find('option').filter(function () {
+        return $(this).val() === item.id;
+      });
+
+      // If an existing option wasn't found for it, create the option
+      if (!$existingOptions.length) {
+        var $option = self.option(item);
+        $option.attr('data-select2-tag', true);
+
+        self._removeOldTags();
+        self.addOptions([$option]);
+      }
+
+      // Select the item, now that we know there is an option for it
+      select(item);
+    }
+
+    function select (data) {
+      self.trigger('select', {
+        data: data
+      });
+    }
+
+    params.term = params.term || '';
+
+    var tokenData = this.tokenizer(params, this.options, createAndSelect);
+
+    if (tokenData.term !== params.term) {
+      // Replace the search term if we have the search box
+      if (this.$search.length) {
+        this.$search.val(tokenData.term);
+        this.$search.focus();
+      }
+
+      params.term = tokenData.term;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
+    var separators = options.get('tokenSeparators') || [];
+    var term = params.term;
+    var i = 0;
+
+    var createTag = this.createTag || function (params) {
+      return {
+        id: params.term,
+        text: params.term
+      };
+    };
+
+    while (i < term.length) {
+      var termChar = term[i];
+
+      if ($.inArray(termChar, separators) === -1) {
+        i++;
+
+        continue;
+      }
+
+      var part = term.substr(0, i);
+      var partParams = $.extend({}, params, {
+        term: part
+      });
+
+      var data = createTag(partParams);
+
+      if (data == null) {
+        i++;
+        continue;
+      }
+
+      callback(data);
+
+      // Reset the term to not include the tokenized portion
+      term = term.substr(i + 1) || '';
+      i = 0;
+    }
+
+    return {
+      term: term
+    };
+  };
+
+  return Tokenizer;
+});
+
+S2.define('select2/data/minimumInputLength',[
+
+], function () {
+  function MinimumInputLength (decorated, $e, options) {
+    this.minimumInputLength = options.get('minimumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MinimumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (params.term.length < this.minimumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooShort',
+        args: {
+          minimum: this.minimumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MinimumInputLength;
+});
+
+S2.define('select2/data/maximumInputLength',[
+
+], function () {
+  function MaximumInputLength (decorated, $e, options) {
+    this.maximumInputLength = options.get('maximumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (this.maximumInputLength > 0 &&
+        params.term.length > this.maximumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooLong',
+        args: {
+          maximum: this.maximumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MaximumInputLength;
+});
+
+S2.define('select2/data/maximumSelectionLength',[
+
+], function (){
+  function MaximumSelectionLength (decorated, $e, options) {
+    this.maximumSelectionLength = options.get('maximumSelectionLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumSelectionLength.prototype.query =
+    function (decorated, params, callback) {
+      var self = this;
+
+      this.current(function (currentData) {
+        var count = currentData != null ? currentData.length : 0;
+        if (self.maximumSelectionLength > 0 &&
+          count >= self.maximumSelectionLength) {
+          self.trigger('results:message', {
+            message: 'maximumSelected',
+            args: {
+              maximum: self.maximumSelectionLength
+            }
+          });
+          return;
+        }
+        decorated.call(self, params, callback);
+      });
+  };
+
+  return MaximumSelectionLength;
+});
+
+S2.define('select2/dropdown',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Dropdown ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    Dropdown.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Dropdown, Utils.Observable);
+
+  Dropdown.prototype.render = function () {
+    var $dropdown = $(
+      '<span class="select2-dropdown">' +
+        '<span class="select2-results"></span>' +
+      '</span>'
+    );
+
+    $dropdown.attr('dir', this.options.get('dir'));
+
+    this.$dropdown = $dropdown;
+
+    return $dropdown;
+  };
+
+  Dropdown.prototype.bind = function () {
+    // Should be implemented in subclasses
+  };
+
+  Dropdown.prototype.position = function ($dropdown, $container) {
+    // Should be implmented in subclasses
+  };
+
+  Dropdown.prototype.destroy = function () {
+    // Remove the dropdown from the DOM
+    this.$dropdown.remove();
+  };
+
+  return Dropdown;
+});
+
+S2.define('select2/dropdown/search',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function Search () { }
+
+  Search.prototype.render = function (decorated) {
+    var $rendered = decorated.call(this);
+
+    var $search = $(
+      '<span class="select2-search select2-search--dropdown">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" />' +
+      '</span>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    $rendered.prepend($search);
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    this.$search.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+    });
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$search.on('input', function (evt) {
+      // Unbind the duplicated `keyup` event
+      $(this).off('keyup');
+    });
+
+    this.$search.on('keyup input', function (evt) {
+      self.handleSearch(evt);
+    });
+
+    container.on('open', function () {
+      self.$search.attr('tabindex', 0);
+
+      self.$search.focus();
+
+      window.setTimeout(function () {
+        self.$search.focus();
+      }, 0);
+    });
+
+    container.on('close', function () {
+      self.$search.attr('tabindex', -1);
+
+      self.$search.val('');
+    });
+
+    container.on('focus', function () {
+      if (!container.isOpen()) {
+        self.$search.focus();
+      }
+    });
+
+    container.on('results:all', function (params) {
+      if (params.query.term == null || params.query.term === '') {
+        var showSearch = self.showSearch(params);
+
+        if (showSearch) {
+          self.$searchContainer.removeClass('select2-search--hide');
+        } else {
+          self.$searchContainer.addClass('select2-search--hide');
+        }
+      }
+    });
+  };
+
+  Search.prototype.handleSearch = function (evt) {
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.showSearch = function (_, params) {
+    return true;
+  };
+
+  return Search;
+});
+
+S2.define('select2/dropdown/hidePlaceholder',[
+
+], function () {
+  function HidePlaceholder (decorated, $element, options, dataAdapter) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  HidePlaceholder.prototype.append = function (decorated, data) {
+    data.results = this.removePlaceholder(data.results);
+
+    decorated.call(this, data);
+  };
+
+  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  HidePlaceholder.prototype.removePlaceholder = function (_, data) {
+    var modifiedData = data.slice(0);
+
+    for (var d = data.length - 1; d >= 0; d--) {
+      var item = data[d];
+
+      if (this.placeholder.id === item.id) {
+        modifiedData.splice(d, 1);
+      }
+    }
+
+    return modifiedData;
+  };
+
+  return HidePlaceholder;
+});
+
+S2.define('select2/dropdown/infiniteScroll',[
+  'jquery'
+], function ($) {
+  function InfiniteScroll (decorated, $element, options, dataAdapter) {
+    this.lastParams = {};
+
+    decorated.call(this, $element, options, dataAdapter);
+
+    this.$loadingMore = this.createLoadingMore();
+    this.loading = false;
+  }
+
+  InfiniteScroll.prototype.append = function (decorated, data) {
+    this.$loadingMore.remove();
+    this.loading = false;
+
+    decorated.call(this, data);
+
+    if (this.showLoadingMore(data)) {
+      this.$results.append(this.$loadingMore);
+    }
+  };
+
+  InfiniteScroll.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('query', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    container.on('query:append', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    this.$results.on('scroll', function () {
+      var isLoadMoreVisible = $.contains(
+        document.documentElement,
+        self.$loadingMore[0]
+      );
+
+      if (self.loading || !isLoadMoreVisible) {
+        return;
+      }
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var loadingMoreOffset = self.$loadingMore.offset().top +
+        self.$loadingMore.outerHeight(false);
+
+      if (currentOffset + 50 >= loadingMoreOffset) {
+        self.loadMore();
+      }
+    });
+  };
+
+  InfiniteScroll.prototype.loadMore = function () {
+    this.loading = true;
+
+    var params = $.extend({}, {page: 1}, this.lastParams);
+
+    params.page++;
+
+    this.trigger('query:append', params);
+  };
+
+  InfiniteScroll.prototype.showLoadingMore = function (_, data) {
+    return data.pagination && data.pagination.more;
+  };
+
+  InfiniteScroll.prototype.createLoadingMore = function () {
+    var $option = $(
+      '<li ' +
+      'class="select2-results__option select2-results__option--load-more"' +
+      'role="treeitem" aria-disabled="true"></li>'
+    );
+
+    var message = this.options.get('translations').get('loadingMore');
+
+    $option.html(message(this.lastParams));
+
+    return $option;
+  };
+
+  return InfiniteScroll;
+});
+
+S2.define('select2/dropdown/attachBody',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function AttachBody (decorated, $element, options) {
+    this.$dropdownParent = options.get('dropdownParent') || $(document.body);
+
+    decorated.call(this, $element, options);
+  }
+
+  AttachBody.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    var setupResultsEvents = false;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self._showDropdown();
+      self._attachPositioningHandler(container);
+
+      if (!setupResultsEvents) {
+        setupResultsEvents = true;
+
+        container.on('results:all', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+
+        container.on('results:append', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+      }
+    });
+
+    container.on('close', function () {
+      self._hideDropdown();
+      self._detachPositioningHandler(container);
+    });
+
+    this.$dropdownContainer.on('mousedown', function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  AttachBody.prototype.destroy = function (decorated) {
+    decorated.call(this);
+
+    this.$dropdownContainer.remove();
+  };
+
+  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
+    // Clone all of the container classes
+    $dropdown.attr('class', $container.attr('class'));
+
+    $dropdown.removeClass('select2');
+    $dropdown.addClass('select2-container--open');
+
+    $dropdown.css({
+      position: 'absolute',
+      top: -999999
+    });
+
+    this.$container = $container;
+  };
+
+  AttachBody.prototype.render = function (decorated) {
+    var $container = $('<span></span>');
+
+    var $dropdown = decorated.call(this);
+    $container.append($dropdown);
+
+    this.$dropdownContainer = $container;
+
+    return $container;
+  };
+
+  AttachBody.prototype._hideDropdown = function (decorated) {
+    this.$dropdownContainer.detach();
+  };
+
+  AttachBody.prototype._attachPositioningHandler =
+      function (decorated, container) {
+    var self = this;
+
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.each(function () {
+      $(this).data('select2-scroll-position', {
+        x: $(this).scrollLeft(),
+        y: $(this).scrollTop()
+      });
+    });
+
+    $watchers.on(scrollEvent, function (ev) {
+      var position = $(this).data('select2-scroll-position');
+      $(this).scrollTop(position.y);
+    });
+
+    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
+      function (e) {
+      self._positionDropdown();
+      self._resizeDropdown();
+    });
+  };
+
+  AttachBody.prototype._detachPositioningHandler =
+      function (decorated, container) {
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.off(scrollEvent);
+
+    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
+  };
+
+  AttachBody.prototype._positionDropdown = function () {
+    var $window = $(window);
+
+    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
+    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
+
+    var newDirection = null;
+
+    var offset = this.$container.offset();
+
+    offset.bottom = offset.top + this.$container.outerHeight(false);
+
+    var container = {
+      height: this.$container.outerHeight(false)
+    };
+
+    container.top = offset.top;
+    container.bottom = offset.top + container.height;
+
+    var dropdown = {
+      height: this.$dropdown.outerHeight(false)
+    };
+
+    var viewport = {
+      top: $window.scrollTop(),
+      bottom: $window.scrollTop() + $window.height()
+    };
+
+    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
+    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
+
+    var css = {
+      left: offset.left,
+      top: container.bottom
+    };
+
+    // Determine what the parent element is to use for calciulating the offset
+    var $offsetParent = this.$dropdownParent;
+
+    // For statically positoned elements, we need to get the element
+    // that is determining the offset
+    if ($offsetParent.css('position') === 'static') {
+      $offsetParent = $offsetParent.offsetParent();
+    }
+
+    var parentOffset = $offsetParent.offset();
+
+    css.top -= parentOffset.top;
+    css.left -= parentOffset.left;
+
+    if (!isCurrentlyAbove && !isCurrentlyBelow) {
+      newDirection = 'below';
+    }
+
+    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
+      newDirection = 'above';
+    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
+      newDirection = 'below';
+    }
+
+    if (newDirection == 'above' ||
+      (isCurrentlyAbove && newDirection !== 'below')) {
+      css.top = container.top - parentOffset.top - dropdown.height;
+    }
+
+    if (newDirection != null) {
+      this.$dropdown
+        .removeClass('select2-dropdown--below select2-dropdown--above')
+        .addClass('select2-dropdown--' + newDirection);
+      this.$container
+        .removeClass('select2-container--below select2-container--above')
+        .addClass('select2-container--' + newDirection);
+    }
+
+    this.$dropdownContainer.css(css);
+  };
+
+  AttachBody.prototype._resizeDropdown = function () {
+    var css = {
+      width: this.$container.outerWidth(false) + 'px'
+    };
+
+    if (this.options.get('dropdownAutoWidth')) {
+      css.minWidth = css.width;
+      css.position = 'relative';
+      css.width = 'auto';
+    }
+
+    this.$dropdown.css(css);
+  };
+
+  AttachBody.prototype._showDropdown = function (decorated) {
+    this.$dropdownContainer.appendTo(this.$dropdownParent);
+
+    this._positionDropdown();
+    this._resizeDropdown();
+  };
+
+  return AttachBody;
+});
+
+S2.define('select2/dropdown/minimumResultsForSearch',[
+
+], function () {
+  function countResults (data) {
+    var count = 0;
+
+    for (var d = 0; d < data.length; d++) {
+      var item = data[d];
+
+      if (item.children) {
+        count += countResults(item.children);
+      } else {
+        count++;
+      }
+    }
+
+    return count;
+  }
+
+  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
+    this.minimumResultsForSearch = options.get('minimumResultsForSearch');
+
+    if (this.minimumResultsForSearch < 0) {
+      this.minimumResultsForSearch = Infinity;
+    }
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
+    if (countResults(params.data.results) < this.minimumResultsForSearch) {
+      return false;
+    }
+
+    return decorated.call(this, params);
+  };
+
+  return MinimumResultsForSearch;
+});
+
+S2.define('select2/dropdown/selectOnClose',[
+
+], function () {
+  function SelectOnClose () { }
+
+  SelectOnClose.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('close', function (params) {
+      self._handleSelectOnClose(params);
+    });
+  };
+
+  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
+    if (params && params.originalSelect2Event != null) {
+      var event = params.originalSelect2Event;
+
+      // Don't select an item if the close event was triggered from a select or
+      // unselect event
+      if (event._type === 'select' || event._type === 'unselect') {
+        return;
+      }
+    }
+
+    var $highlightedResults = this.getHighlightedResults();
+
+    // Only select highlighted results
+    if ($highlightedResults.length < 1) {
+      return;
+    }
+
+    var data = $highlightedResults.data('data');
+
+    // Don't re-select already selected resulte
+    if (
+      (data.element != null && data.element.selected) ||
+      (data.element == null && data.selected)
+    ) {
+      return;
+    }
+
+    this.trigger('select', {
+        data: data
+    });
+  };
+
+  return SelectOnClose;
+});
+
+S2.define('select2/dropdown/closeOnSelect',[
+
+], function () {
+  function CloseOnSelect () { }
+
+  CloseOnSelect.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('select', function (evt) {
+      self._selectTriggered(evt);
+    });
+
+    container.on('unselect', function (evt) {
+      self._selectTriggered(evt);
+    });
+  };
+
+  CloseOnSelect.prototype._selectTriggered = function (_, evt) {
+    var originalEvent = evt.originalEvent;
+
+    // Don't close if the control key is being held
+    if (originalEvent && originalEvent.ctrlKey) {
+      return;
+    }
+
+    this.trigger('close', {
+      originalEvent: originalEvent,
+      originalSelect2Event: evt
+    });
+  };
+
+  return CloseOnSelect;
+});
+
+S2.define('select2/i18n/en',[],function () {
+  // English
+  return {
+    errorLoading: function () {
+      return 'The results could not be loaded.';
+    },
+    inputTooLong: function (args) {
+      var overChars = args.input.length - args.maximum;
+
+      var message = 'Please delete ' + overChars + ' character';
+
+      if (overChars != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    inputTooShort: function (args) {
+      var remainingChars = args.minimum - args.input.length;
+
+      var message = 'Please enter ' + remainingChars + ' or more characters';
+
+      return message;
+    },
+    loadingMore: function () {
+      return 'Loading more results…';
+    },
+    maximumSelected: function (args) {
+      var message = 'You can only select ' + args.maximum + ' item';
+
+      if (args.maximum != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    noResults: function () {
+      return 'No results found';
+    },
+    searching: function () {
+      return 'Searching…';
+    }
+  };
+});
+
+S2.define('select2/defaults',[
+  'jquery',
+  'require',
+
+  './results',
+
+  './selection/single',
+  './selection/multiple',
+  './selection/placeholder',
+  './selection/allowClear',
+  './selection/search',
+  './selection/eventRelay',
+
+  './utils',
+  './translation',
+  './diacritics',
+
+  './data/select',
+  './data/array',
+  './data/ajax',
+  './data/tags',
+  './data/tokenizer',
+  './data/minimumInputLength',
+  './data/maximumInputLength',
+  './data/maximumSelectionLength',
+
+  './dropdown',
+  './dropdown/search',
+  './dropdown/hidePlaceholder',
+  './dropdown/infiniteScroll',
+  './dropdown/attachBody',
+  './dropdown/minimumResultsForSearch',
+  './dropdown/selectOnClose',
+  './dropdown/closeOnSelect',
+
+  './i18n/en'
+], function ($, require,
+
+             ResultsList,
+
+             SingleSelection, MultipleSelection, Placeholder, AllowClear,
+             SelectionSearch, EventRelay,
+
+             Utils, Translation, DIACRITICS,
+
+             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
+             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
+
+             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
+             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
+
+             EnglishTranslation) {
+  function Defaults () {
+    this.reset();
+  }
+
+  Defaults.prototype.apply = function (options) {
+    options = $.extend(true, {}, this.defaults, options);
+
+    if (options.dataAdapter == null) {
+      if (options.ajax != null) {
+        options.dataAdapter = AjaxData;
+      } else if (options.data != null) {
+        options.dataAdapter = ArrayData;
+      } else {
+        options.dataAdapter = SelectData;
+      }
+
+      if (options.minimumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MinimumInputLength
+        );
+      }
+
+      if (options.maximumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumInputLength
+        );
+      }
+
+      if (options.maximumSelectionLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumSelectionLength
+        );
+      }
+
+      if (options.tags) {
+        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
+      }
+
+      if (options.tokenSeparators != null || options.tokenizer != null) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Tokenizer
+        );
+      }
+
+      if (options.query != null) {
+        var Query = require(options.amdBase + 'compat/query');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Query
+        );
+      }
+
+      if (options.initSelection != null) {
+        var InitSelection = require(options.amdBase + 'compat/initSelection');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          InitSelection
+        );
+      }
+    }
+
+    if (options.resultsAdapter == null) {
+      options.resultsAdapter = ResultsList;
+
+      if (options.ajax != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          InfiniteScroll
+        );
+      }
+
+      if (options.placeholder != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          HidePlaceholder
+        );
+      }
+
+      if (options.selectOnClose) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          SelectOnClose
+        );
+      }
+    }
+
+    if (options.dropdownAdapter == null) {
+      if (options.multiple) {
+        options.dropdownAdapter = Dropdown;
+      } else {
+        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
+
+        options.dropdownAdapter = SearchableDropdown;
+      }
+
+      if (options.minimumResultsForSearch !== 0) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          MinimumResultsForSearch
+        );
+      }
+
+      if (options.closeOnSelect) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          CloseOnSelect
+        );
+      }
+
+      if (
+        options.dropdownCssClass != null ||
+        options.dropdownCss != null ||
+        options.adaptDropdownCssClass != null
+      ) {
+        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
+
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          DropdownCSS
+        );
+      }
+
+      options.dropdownAdapter = Utils.Decorate(
+        options.dropdownAdapter,
+        AttachBody
+      );
+    }
+
+    if (options.selectionAdapter == null) {
+      if (options.multiple) {
+        options.selectionAdapter = MultipleSelection;
+      } else {
+        options.selectionAdapter = SingleSelection;
+      }
+
+      // Add the placeholder mixin if a placeholder was specified
+      if (options.placeholder != null) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          Placeholder
+        );
+      }
+
+      if (options.allowClear) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          AllowClear
+        );
+      }
+
+      if (options.multiple) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          SelectionSearch
+        );
+      }
+
+      if (
+        options.containerCssClass != null ||
+        options.containerCss != null ||
+        options.adaptContainerCssClass != null
+      ) {
+        var ContainerCSS = require(options.amdBase + 'compat/containerCss');
+
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          ContainerCSS
+        );
+      }
+
+      options.selectionAdapter = Utils.Decorate(
+        options.selectionAdapter,
+        EventRelay
+      );
+    }
+
+    if (typeof options.language === 'string') {
+      // Check if the language is specified with a region
+      if (options.language.indexOf('-') > 0) {
+        // Extract the region information if it is included
+        var languageParts = options.language.split('-');
+        var baseLanguage = languageParts[0];
+
+        options.language = [options.language, baseLanguage];
+      } else {
+        options.language = [options.language];
+      }
+    }
+
+    if ($.isArray(options.language)) {
+      var languages = new Translation();
+      options.language.push('en');
+
+      var languageNames = options.language;
+
+      for (var l = 0; l < languageNames.length; l++) {
+        var name = languageNames[l];
+        var language = {};
+
+        try {
+          // Try to load it with the original name
+          language = Translation.loadPath(name);
+        } catch (e) {
+          try {
+            // If we couldn't load it, check if it wasn't the full path
+            name = this.defaults.amdLanguageBase + name;
+            language = Translation.loadPath(name);
+          } catch (ex) {
+            // The translation could not be loaded at all. Sometimes this is
+            // because of a configuration problem, other times this can be
+            // because of how Select2 helps load all possible translation files.
+            if (options.debug && window.console && console.warn) {
+              console.warn(
+                'Select2: The language file for "' + name + '" could not be ' +
+                'automatically loaded. A fallback will be used instead.'
+              );
+            }
+
+            continue;
+          }
+        }
+
+        languages.extend(language);
+      }
+
+      options.translations = languages;
+    } else {
+      var baseTranslation = Translation.loadPath(
+        this.defaults.amdLanguageBase + 'en'
+      );
+      var customTranslation = new Translation(options.language);
+
+      customTranslation.extend(baseTranslation);
+
+      options.translations = customTranslation;
+    }
+
+    return options;
+  };
+
+  Defaults.prototype.reset = function () {
+    function stripDiacritics (text) {
+      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
+      function match(a) {
+        return DIACRITICS[a] || a;
+      }
+
+      return text.replace(/[^\u0000-\u007E]/g, match);
+    }
+
+    function matcher (params, data) {
+      // Always return the object if there is nothing to compare
+      if ($.trim(params.term) === '') {
+        return data;
+      }
+
+      // Do a recursive check for options with children
+      if (data.children && data.children.length > 0) {
+        // Clone the data object if there are children
+        // This is required as we modify the object to remove any non-matches
+        var match = $.extend(true, {}, data);
+
+        // Check each child of the option
+        for (var c = data.children.length - 1; c >= 0; c--) {
+          var child = data.children[c];
+
+          var matches = matcher(params, child);
+
+          // If there wasn't a match, remove the object in the array
+          if (matches == null) {
+            match.children.splice(c, 1);
+          }
+        }
+
+        // If any children matched, return the new object
+        if (match.children.length > 0) {
+          return match;
+        }
+
+        // If there were no matching children, check just the plain object
+        return matcher(params, match);
+      }
+
+      var original = stripDiacritics(data.text).toUpperCase();
+      var term = stripDiacritics(params.term).toUpperCase();
+
+      // Check if the text contains the term
+      if (original.indexOf(term) > -1) {
+        return data;
+      }
+
+      // If it doesn't contain the term, don't return anything
+      return null;
+    }
+
+    this.defaults = {
+      amdBase: './',
+      amdLanguageBase: './i18n/',
+      closeOnSelect: true,
+      debug: false,
+      dropdownAutoWidth: false,
+      escapeMarkup: Utils.escapeMarkup,
+      language: EnglishTranslation,
+      matcher: matcher,
+      minimumInputLength: 0,
+      maximumInputLength: 0,
+      maximumSelectionLength: 0,
+      minimumResultsForSearch: 0,
+      selectOnClose: false,
+      sorter: function (data) {
+        return data;
+      },
+      templateResult: function (result) {
+        return result.text;
+      },
+      templateSelection: function (selection) {
+        return selection.text;
+      },
+      theme: 'default',
+      width: 'resolve'
+    };
+  };
+
+  Defaults.prototype.set = function (key, value) {
+    var camelKey = $.camelCase(key);
+
+    var data = {};
+    data[camelKey] = value;
+
+    var convertedData = Utils._convertData(data);
+
+    $.extend(this.defaults, convertedData);
+  };
+
+  var defaults = new Defaults();
+
+  return defaults;
+});
+
+S2.define('select2/options',[
+  'require',
+  'jquery',
+  './defaults',
+  './utils'
+], function (require, $, Defaults, Utils) {
+  function Options (options, $element) {
+    this.options = options;
+
+    if ($element != null) {
+      this.fromElement($element);
+    }
+
+    this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
+  }
+
+  Options.prototype.fromElement = function ($e) {
+    var excludedData = ['select2'];
+
+    if (this.options.multiple == null) {
+      this.options.multiple = $e.prop('multiple');
+    }
+
+    if (this.options.disabled == null) {
+      this.options.disabled = $e.prop('disabled');
+    }
+
+    if (this.options.language == null) {
+      if ($e.prop('lang')) {
+        this.options.language = $e.prop('lang').toLowerCase();
+      } else if ($e.closest('[lang]').prop('lang')) {
+        this.options.language = $e.closest('[lang]').prop('lang');
+      }
+    }
+
+    if (this.options.dir == null) {
+      if ($e.prop('dir')) {
+        this.options.dir = $e.prop('dir');
+      } else if ($e.closest('[dir]').prop('dir')) {
+        this.options.dir = $e.closest('[dir]').prop('dir');
+      } else {
+        this.options.dir = 'ltr';
+      }
+    }
+
+    $e.prop('disabled', this.options.disabled);
+    $e.prop('multiple', this.options.multiple);
+
+    if ($e.data('select2Tags')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-select2-tags` attribute has been changed to ' +
+          'use the `data-data` and `data-tags="true"` attributes and will be ' +
+          'removed in future versions of Select2.'
+        );
+      }
+
+      $e.data('data', $e.data('select2Tags'));
+      $e.data('tags', true);
+    }
+
+    if ($e.data('ajaxUrl')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-ajax-url` attribute has been changed to ' +
+          '`data-ajax--url` and support for the old attribute will be removed' +
+          ' in future versions of Select2.'
+        );
+      }
+
+      $e.attr('ajax--url', $e.data('ajaxUrl'));
+      $e.data('ajax--url', $e.data('ajaxUrl'));
+    }
+
+    var dataset = {};
+
+    // Prefer the element's `dataset` attribute if it exists
+    // jQuery 1.x does not correctly handle data attributes with multiple dashes
+    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
+      dataset = $.extend(true, {}, $e[0].dataset, $e.data());
+    } else {
+      dataset = $e.data();
+    }
+
+    var data = $.extend(true, {}, dataset);
+
+    data = Utils._convertData(data);
+
+    for (var key in data) {
+      if ($.inArray(key, excludedData) > -1) {
+        continue;
+      }
+
+      if ($.isPlainObject(this.options[key])) {
+        $.extend(this.options[key], data[key]);
+      } else {
+        this.options[key] = data[key];
+      }
+    }
+
+    return this;
+  };
+
+  Options.prototype.get = function (key) {
+    return this.options[key];
+  };
+
+  Options.prototype.set = function (key, val) {
+    this.options[key] = val;
+  };
+
+  return Options;
+});
+
+S2.define('select2/core',[
+  'jquery',
+  './options',
+  './utils',
+  './keys'
+], function ($, Options, Utils, KEYS) {
+  var Select2 = function ($element, options) {
+    if ($element.data('select2') != null) {
+      $element.data('select2').destroy();
+    }
+
+    this.$element = $element;
+
+    this.id = this._generateId($element);
+
+    options = options || {};
+
+    this.options = new Options(options, $element);
+
+    Select2.__super__.constructor.call(this);
+
+    // Set up the tabindex
+
+    var tabindex = $element.attr('tabindex') || 0;
+    $element.data('old-tabindex', tabindex);
+    $element.attr('tabindex', '-1');
+
+    // Set up containers and adapters
+
+    var DataAdapter = this.options.get('dataAdapter');
+    this.dataAdapter = new DataAdapter($element, this.options);
+
+    var $container = this.render();
+
+    this._placeContainer($container);
+
+    var SelectionAdapter = this.options.get('selectionAdapter');
+    this.selection = new SelectionAdapter($element, this.options);
+    this.$selection = this.selection.render();
+
+    this.selection.position(this.$selection, $container);
+
+    var DropdownAdapter = this.options.get('dropdownAdapter');
+    this.dropdown = new DropdownAdapter($element, this.options);
+    this.$dropdown = this.dropdown.render();
+
+    this.dropdown.position(this.$dropdown, $container);
+
+    var ResultsAdapter = this.options.get('resultsAdapter');
+    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
+    this.$results = this.results.render();
+
+    this.results.position(this.$results, this.$dropdown);
+
+    // Bind events
+
+    var self = this;
+
+    // Bind the container to all of the adapters
+    this._bindAdapters();
+
+    // Register any DOM event handlers
+    this._registerDomEvents();
+
+    // Register any internal event handlers
+    this._registerDataEvents();
+    this._registerSelectionEvents();
+    this._registerDropdownEvents();
+    this._registerResultsEvents();
+    this._registerEvents();
+
+    // Set the initial state
+    this.dataAdapter.current(function (initialData) {
+      self.trigger('selection:update', {
+        data: initialData
+      });
+    });
+
+    // Hide the original select
+    $element.addClass('select2-hidden-accessible');
+    $element.attr('aria-hidden', 'true');
+
+    // Synchronize any monitored attributes
+    this._syncAttributes();
+
+    $element.data('select2', this);
+  };
+
+  Utils.Extend(Select2, Utils.Observable);
+
+  Select2.prototype._generateId = function ($element) {
+    var id = '';
+
+    if ($element.attr('id') != null) {
+      id = $element.attr('id');
+    } else if ($element.attr('name') != null) {
+      id = $element.attr('name') + '-' + Utils.generateChars(2);
+    } else {
+      id = Utils.generateChars(4);
+    }
+
+    id = id.replace(/(:|\.|\[|\]|,)/g, '');
+    id = 'select2-' + id;
+
+    return id;
+  };
+
+  Select2.prototype._placeContainer = function ($container) {
+    $container.insertAfter(this.$element);
+
+    var width = this._resolveWidth(this.$element, this.options.get('width'));
+
+    if (width != null) {
+      $container.css('width', width);
+    }
+  };
+
+  Select2.prototype._resolveWidth = function ($element, method) {
+    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
+
+    if (method == 'resolve') {
+      var styleWidth = this._resolveWidth($element, 'style');
+
+      if (styleWidth != null) {
+        return styleWidth;
+      }
+
+      return this._resolveWidth($element, 'element');
+    }
+
+    if (method == 'element') {
+      var elementWidth = $element.outerWidth(false);
+
+      if (elementWidth <= 0) {
+        return 'auto';
+      }
+
+      return elementWidth + 'px';
+    }
+
+    if (method == 'style') {
+      var style = $element.attr('style');
+
+      if (typeof(style) !== 'string') {
+        return null;
+      }
+
+      var attrs = style.split(';');
+
+      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
+        var attr = attrs[i].replace(/\s/g, '');
+        var matches = attr.match(WIDTH);
+
+        if (matches !== null && matches.length >= 1) {
+          return matches[1];
+        }
+      }
+
+      return null;
+    }
+
+    return method;
+  };
+
+  Select2.prototype._bindAdapters = function () {
+    this.dataAdapter.bind(this, this.$container);
+    this.selection.bind(this, this.$container);
+
+    this.dropdown.bind(this, this.$container);
+    this.results.bind(this, this.$container);
+  };
+
+  Select2.prototype._registerDomEvents = function () {
+    var self = this;
+
+    this.$element.on('change.select2', function () {
+      self.dataAdapter.current(function (data) {
+        self.trigger('selection:update', {
+          data: data
+        });
+      });
+    });
+
+    this.$element.on('focus.select2', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this._syncA = Utils.bind(this._syncAttributes, this);
+    this._syncS = Utils.bind(this._syncSubtree, this);
+
+    if (this.$element[0].attachEvent) {
+      this.$element[0].attachEvent('onpropertychange', this._syncA);
+    }
+
+    var observer = window.MutationObserver ||
+      window.WebKitMutationObserver ||
+      window.MozMutationObserver
+    ;
+
+    if (observer != null) {
+      this._observer = new observer(function (mutations) {
+        $.each(mutations, self._syncA);
+        $.each(mutations, self._syncS);
+      });
+      this._observer.observe(this.$element[0], {
+        attributes: true,
+        childList: true,
+        subtree: false
+      });
+    } else if (this.$element[0].addEventListener) {
+      this.$element[0].addEventListener(
+        'DOMAttrModified',
+        self._syncA,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeInserted',
+        self._syncS,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeRemoved',
+        self._syncS,
+        false
+      );
+    }
+  };
+
+  Select2.prototype._registerDataEvents = function () {
+    var self = this;
+
+    this.dataAdapter.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerSelectionEvents = function () {
+    var self = this;
+    var nonRelayEvents = ['toggle', 'focus'];
+
+    this.selection.on('toggle', function () {
+      self.toggleDropdown();
+    });
+
+    this.selection.on('focus', function (params) {
+      self.focus(params);
+    });
+
+    this.selection.on('*', function (name, params) {
+      if ($.inArray(name, nonRelayEvents) !== -1) {
+        return;
+      }
+
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerDropdownEvents = function () {
+    var self = this;
+
+    this.dropdown.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerResultsEvents = function () {
+    var self = this;
+
+    this.results.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerEvents = function () {
+    var self = this;
+
+    this.on('open', function () {
+      self.$container.addClass('select2-container--open');
+    });
+
+    this.on('close', function () {
+      self.$container.removeClass('select2-container--open');
+    });
+
+    this.on('enable', function () {
+      self.$container.removeClass('select2-container--disabled');
+    });
+
+    this.on('disable', function () {
+      self.$container.addClass('select2-container--disabled');
+    });
+
+    this.on('blur', function () {
+      self.$container.removeClass('select2-container--focus');
+    });
+
+    this.on('query', function (params) {
+      if (!self.isOpen()) {
+        self.trigger('open', {});
+      }
+
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:all', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('query:append', function (params) {
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:append', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('keypress', function (evt) {
+      var key = evt.which;
+
+      if (self.isOpen()) {
+        if (key === KEYS.ESC || key === KEYS.TAB ||
+            (key === KEYS.UP && evt.altKey)) {
+          self.close();
+
+          evt.preventDefault();
+        } else if (key === KEYS.ENTER) {
+          self.trigger('results:select', {});
+
+          evt.preventDefault();
+        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
+          self.trigger('results:toggle', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.UP) {
+          self.trigger('results:previous', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.DOWN) {
+          self.trigger('results:next', {});
+
+          evt.preventDefault();
+        }
+      } else {
+        if (key === KEYS.ENTER || key === KEYS.SPACE ||
+            (key === KEYS.DOWN && evt.altKey)) {
+          self.open();
+
+          evt.preventDefault();
+        }
+      }
+    });
+  };
+
+  Select2.prototype._syncAttributes = function () {
+    this.options.set('disabled', this.$element.prop('disabled'));
+
+    if (this.options.get('disabled')) {
+      if (this.isOpen()) {
+        this.close();
+      }
+
+      this.trigger('disable', {});
+    } else {
+      this.trigger('enable', {});
+    }
+  };
+
+  Select2.prototype._syncSubtree = function (evt, mutations) {
+    var changed = false;
+    var self = this;
+
+    // Ignore any mutation events raised for elements that aren't options or
+    // optgroups. This handles the case when the select element is destroyed
+    if (
+      evt && evt.target && (
+        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
+      )
+    ) {
+      return;
+    }
+
+    if (!mutations) {
+      // If mutation events aren't supported, then we can only assume that the
+      // change affected the selections
+      changed = true;
+    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
+      for (var n = 0; n < mutations.addedNodes.length; n++) {
+        var node = mutations.addedNodes[n];
+
+        if (node.selected) {
+          changed = true;
+        }
+      }
+    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
+      changed = true;
+    }
+
+    // Only re-pull the data if we think there is a change
+    if (changed) {
+      this.dataAdapter.current(function (currentData) {
+        self.trigger('selection:update', {
+          data: currentData
+        });
+      });
+    }
+  };
+
+  /**
+   * Override the trigger method to automatically trigger pre-events when
+   * there are events that can be prevented.
+   */
+  Select2.prototype.trigger = function (name, args) {
+    var actualTrigger = Select2.__super__.trigger;
+    var preTriggerMap = {
+      'open': 'opening',
+      'close': 'closing',
+      'select': 'selecting',
+      'unselect': 'unselecting'
+    };
+
+    if (args === undefined) {
+      args = {};
+    }
+
+    if (name in preTriggerMap) {
+      var preTriggerName = preTriggerMap[name];
+      var preTriggerArgs = {
+        prevented: false,
+        name: name,
+        args: args
+      };
+
+      actualTrigger.call(this, preTriggerName, preTriggerArgs);
+
+      if (preTriggerArgs.prevented) {
+        args.prevented = true;
+
+        return;
+      }
+    }
+
+    actualTrigger.call(this, name, args);
+  };
+
+  Select2.prototype.toggleDropdown = function () {
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    if (this.isOpen()) {
+      this.close();
+    } else {
+      this.open();
+    }
+  };
+
+  Select2.prototype.open = function () {
+    if (this.isOpen()) {
+      return;
+    }
+
+    this.trigger('query', {});
+  };
+
+  Select2.prototype.close = function () {
+    if (!this.isOpen()) {
+      return;
+    }
+
+    this.trigger('close', {});
+  };
+
+  Select2.prototype.isOpen = function () {
+    return this.$container.hasClass('select2-container--open');
+  };
+
+  Select2.prototype.hasFocus = function () {
+    return this.$container.hasClass('select2-container--focus');
+  };
+
+  Select2.prototype.focus = function (data) {
+    // No need to re-trigger focus events if we are already focused
+    if (this.hasFocus()) {
+      return;
+    }
+
+    this.$container.addClass('select2-container--focus');
+    this.trigger('focus', {});
+  };
+
+  Select2.prototype.enable = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("enable")` method has been deprecated and will' +
+        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
+        ' instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      args = [true];
+    }
+
+    var disabled = !args[0];
+
+    this.$element.prop('disabled', disabled);
+  };
+
+  Select2.prototype.data = function () {
+    if (this.options.get('debug') &&
+        arguments.length > 0 && window.console && console.warn) {
+      console.warn(
+        'Select2: Data can no longer be set using `select2("data")`. You ' +
+        'should consider setting the value instead using `$element.val()`.'
+      );
+    }
+
+    var data = [];
+
+    this.dataAdapter.current(function (currentData) {
+      data = currentData;
+    });
+
+    return data;
+  };
+
+  Select2.prototype.val = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("val")` method has been deprecated and will be' +
+        ' removed in later Select2 versions. Use $element.val() instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      return this.$element.val();
+    }
+
+    var newVal = args[0];
+
+    if ($.isArray(newVal)) {
+      newVal = $.map(newVal, function (obj) {
+        return obj.toString();
+      });
+    }
+
+    this.$element.val(newVal).trigger('change');
+  };
+
+  Select2.prototype.destroy = function () {
+    this.$container.remove();
+
+    if (this.$element[0].detachEvent) {
+      this.$element[0].detachEvent('onpropertychange', this._syncA);
+    }
+
+    if (this._observer != null) {
+      this._observer.disconnect();
+      this._observer = null;
+    } else if (this.$element[0].removeEventListener) {
+      this.$element[0]
+        .removeEventListener('DOMAttrModified', this._syncA, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeInserted', this._syncS, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeRemoved', this._syncS, false);
+    }
+
+    this._syncA = null;
+    this._syncS = null;
+
+    this.$element.off('.select2');
+    this.$element.attr('tabindex', this.$element.data('old-tabindex'));
+
+    this.$element.removeClass('select2-hidden-accessible');
+    this.$element.attr('aria-hidden', 'false');
+    this.$element.removeData('select2');
+
+    this.dataAdapter.destroy();
+    this.selection.destroy();
+    this.dropdown.destroy();
+    this.results.destroy();
+
+    this.dataAdapter = null;
+    this.selection = null;
+    this.dropdown = null;
+    this.results = null;
+  };
+
+  Select2.prototype.render = function () {
+    var $container = $(
+      '<span class="select2 select2-container">' +
+        '<span class="selection"></span>' +
+        '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
+      '</span>'
+    );
+
+    $container.attr('dir', this.options.get('dir'));
+
+    this.$container = $container;
+
+    this.$container.addClass('select2-container--' + this.options.get('theme'));
+
+    $container.data('element', this.$element);
+
+    return $container;
+  };
+
+  return Select2;
+});
+
+S2.define('jquery-mousewheel',[
+  'jquery'
+], function ($) {
+  // Used to shim jQuery.mousewheel for non-full builds.
+  return $;
+});
+
+S2.define('jquery.select2',[
+  'jquery',
+  'jquery-mousewheel',
+
+  './select2/core',
+  './select2/defaults'
+], function ($, _, Select2, Defaults) {
+  if ($.fn.select2 == null) {
+    // All methods that should return the element
+    var thisMethods = ['open', 'close', 'destroy'];
+
+    $.fn.select2 = function (options) {
+      options = options || {};
+
+      if (typeof options === 'object') {
+        this.each(function () {
+          var instanceOptions = $.extend(true, {}, options);
+
+          var instance = new Select2($(this), instanceOptions);
+        });
+
+        return this;
+      } else if (typeof options === 'string') {
+        var ret;
+        var args = Array.prototype.slice.call(arguments, 1);
+
+        this.each(function () {
+          var instance = $(this).data('select2');
+
+          if (instance == null && window.console && console.error) {
+            console.error(
+              'The select2(\'' + options + '\') method was called on an ' +
+              'element that is not using Select2.'
+            );
+          }
+
+          ret = instance[options].apply(instance, args);
+        });
+
+        // Check if we should be returning `this`
+        if ($.inArray(options, thisMethods) > -1) {
+          return this;
+        }
+
+        return ret;
+      } else {
+        throw new Error('Invalid arguments for Select2: ' + options);
+      }
+    };
+  }
+
+  if ($.fn.select2.defaults == null) {
+    $.fn.select2.defaults = Defaults;
+  }
+
+  return Select2;
+});
+
+  // Return the AMD loader configuration so it can be used outside of this file
+  return {
+    define: S2.define,
+    require: S2.require
+  };
+}());
+
+  // Autoload the jQuery bindings
+  // We know that all of the modules exist above this, so we're safe
+  var select2 = S2.require('jquery.select2');
+
+  // Hold the AMD module references on the jQuery function that was just loaded
+  // This allows Select2 to use the internal loader outside of this file, such
+  // as in the language files.
+  jQuery.fn.select2.amd = S2;
+
+  // Return the Select2 instance for anyone who is importing it.
+  return select2;
+}));

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
netbox/project-static/select2-4.0.5/js/select2.min.js


+ 23 - 7
netbox/secrets/forms.py

@@ -1,12 +1,14 @@
 from Crypto.Cipher import PKCS1_OAEP
 from Crypto.PublicKey import RSA
 from django import forms
-from django.db.models import Count
 from taggit.forms import TagField
 
 from dcim.models import Device
 from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldForm
-from utilities.forms import BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField
+from utilities.forms import (
+    APISelect, APISelectMultiple, BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField,
+    StaticSelect2Multiple
+)
 from .models import Secret, SecretRole, UserKey
 
 
@@ -42,6 +44,10 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
         fields = [
             'name', 'slug', 'users', 'groups',
         ]
+        widgets = {
+            'users': StaticSelect2Multiple(),
+            'groups': StaticSelect2Multiple(),
+        }
 
 
 class SecretRoleCSVForm(forms.ModelForm):
@@ -85,6 +91,11 @@ class SecretForm(BootstrapMixin, CustomFieldForm):
         fields = [
             'role', 'name', 'plaintext', 'plaintext2', 'tags',
         ]
+        widgets = {
+            'role': APISelect(
+                api_url="/api/secrets/secret-roles/"
+            )
+        }
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -143,7 +154,10 @@ class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     role = forms.ModelChoiceField(
         queryset=SecretRole.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/secrets/secret-roles/"
+        )
     )
     name = forms.CharField(
         max_length=100,
@@ -163,10 +177,12 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
         label='Search'
     )
     role = FilterChoiceField(
-        queryset=SecretRole.objects.annotate(
-            filter_count=Count('secrets')
-        ),
-        to_field_name='slug'
+        queryset=SecretRole.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/secrets/secret-roles/",
+            value_field="slug",
+        )
     )
 
 

+ 2 - 0
netbox/templates/_base.html

@@ -7,6 +7,7 @@
     <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
     <link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
     <link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.css' %}">
+    <link rel="stylesheet" href="{% static 'select2-4.0.5/css/select2.min.css' %}">
     <link rel="stylesheet" href="{% static 'css/base.css' %}?v{{ settings.VERSION }}">
     <link rel="icon" type="image/png" href="{% static 'img/netbox.ico' %}" />
     <meta charset="UTF-8">
@@ -66,6 +67,7 @@
 <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
 <script src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
 <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
+<script src="{% static 'select2-4.0.5/js/select2.min.js' %}"></script>
 <script src="{% static 'js/forms.js' %}?v{{ settings.VERSION }}"></script>
 <script type="text/javascript">
     var netbox_api_path = "/{{ settings.BASE_PATH }}api/";

+ 2 - 2
netbox/templates/dcim/cable_connect.html

@@ -107,14 +107,14 @@
                         </ul>
                         <div class="tab-content">
                             <div class="tab-pane active" id="search">
-                                {% render_field form.livesearch %}
+                                &nbsp;
                             </div>
                             <div class="tab-pane" id="select">
                                 {% render_field form.termination_b_site %}
                                 {% render_field form.termination_b_rack %}
-                                {% render_field form.termination_b_device %}
                             </div>
                         </div>
+                        {% render_field form.termination_b_device %}
                         {% render_field form.termination_b_type %}
                         {% render_field form.termination_b_id %}
                     </div>

+ 0 - 85
netbox/templates/dcim/device_list.html

@@ -20,88 +20,3 @@
     </div>
 </div>
 {% endblock %}
-
-{% block javascript %}
-<script type="text/javascript">
-$(document).ready(function() {
-
-    var site_list = $('#id_site');
-    var rack_group_list = $('#id_rack_group_id');
-    var rack_list = $('#id_rack_id');
-    var manufacturer_list = $('#id_manufacturer_id');
-    var model_list = $('#id_device_type_id');
-
-    // Update device type options based on selected manufacturer
-    manufacturer_list.change(function() {
-        var selected_manufacturers = $(this).val();
-        if (selected_manufacturers) {
-            model_list.empty();
-            $.ajax({
-                url: netbox_api_path + 'dcim/device-types/?limit=500&manufacturer_id=' + selected_manufacturers.join('&manufacturer_id='),
-                dataType: 'json',
-                success: function (response, status) {
-                    $.each(response["results"], function (index, device_type) {
-                        var option = $("<option></option>").attr("value", device_type.id).text(device_type.model + " (" + device_type.instance_count + ")");
-                        model_list.append(option);
-                    });
-                }
-            });
-        }
-    });
-
-    // Update rack group and rack options based on selected site
-    site_list.change(function() {
-        var selected_sites = $(this).val();
-        if (selected_sites) {
-
-            // Update rack group options
-            rack_group_list.empty();
-            $.ajax({
-                url: netbox_api_path + 'dcim/rack-groups/?limit=500&site=' + selected_sites.join('&site='),
-                dataType: 'json',
-                success: function (response, status) {
-                    $.each(response["results"], function (index, group) {
-                        var option = $("<option></option>").attr("value", group.id).text(group.name);
-                        rack_group_list.append(option);
-                    });
-                }
-            });
-
-            // Update rack options
-            rack_list.empty();
-            rack_list.append($("<option></option>").attr("value", "0").text("None"));
-            $.ajax({
-                url: netbox_api_path + 'dcim/racks/?limit=500&site=' + selected_sites.join('&site='),
-                dataType: 'json',
-                success: function (response, status) {
-                    $.each(response["results"], function (index, rack) {
-                        var option = $("<option></option>").attr("value", rack.id).text(rack.display_name);
-                        rack_list.append(option);
-                    });
-                }
-            });
-
-        }
-    });
-
-    // Update rack options based on selected rack group
-    rack_group_list.change(function() {
-        var selected_rack_groups = $(this).val();
-        if (selected_rack_groups) {
-            rack_list.empty();
-            $.ajax({
-                url: netbox_api_path + 'dcim/racks/?limit=500&group_id=' + selected_rack_groups.join('&group_id='),
-                dataType: 'json',
-                success: function (response, status) {
-                    $.each(response["results"], function (index, rack) {
-                        var option = $("<option></option>").attr("value", rack.id).text(rack.display_name);
-                        rack_list.append(option);
-                    });
-                }
-            });
-        }
-    });
-
-});
-</script>
-{% endblock %}

+ 0 - 29
netbox/templates/dcim/inc/filter_rack_group.html

@@ -1,29 +0,0 @@
-<script type="text/javascript">
-$(document).ready(function() {
-
-    var site_list = $('#id_site');
-    var rack_group_list = $('#id_group_id');
-
-    // Update rack group and rack options based on selected site
-    site_list.change(function() {
-        var selected_sites = $(this).val();
-        if (selected_sites) {
-
-            // Update rack group options
-            rack_group_list.empty();
-            $.ajax({
-                url: netbox_api_path + 'dcim/rack-groups/?limit=500&site=' + selected_sites.join('&site='),
-                dataType: 'json',
-                success: function (response, status) {
-                    $.each(response["results"], function (index, group) {
-                        var option = $("<option></option>").attr("value", group.id).text(group.name);
-                        rack_group_list.append(option);
-                    });
-                }
-            });
-
-        }
-    });
-
-});
-</script>

+ 0 - 5
netbox/templates/dcim/rack_list.html

@@ -20,8 +20,3 @@
     </div>
 </div>
 {% endblock %}
-
-{% block javascript %}
-    {% include 'dcim/inc/filter_rack_group.html' %}
-{% endblock %}
-

+ 1 - 1
netbox/templates/ipam/ipaddress_edit.html

@@ -60,7 +60,7 @@
                     {% render_field form.nat_device %}
                 </div>
                 <div class="tab-pane" id="search">
-                    {% render_field form.livesearch %}
+                    &nbsp;
                 </div>
             </div>
             {% render_field form.nat_inside %}

+ 0 - 29
netbox/templates/virtualization/virtualmachine_list.html

@@ -20,32 +20,3 @@
     </div>
 </div>
 {% endblock %}
-
-{% block javascript %}
-<script type="text/javascript">
-$(document).ready(function() {
-
-    var cluster_group_list = $('#id_cluster_group');
-    var cluster_list = $('#id_cluster_id');
-
-    // Update cluster options based on selected group
-    cluster_group_list.change(function() {
-        var selected_groups = $(this).val();
-        if (selected_groups) {
-            cluster_list.empty();
-            $.ajax({
-                url: netbox_api_path + 'virtualization/clusters/?limit=500&group=' + selected_groups.join('&group='),
-                dataType: 'json',
-                success: function (response, status) {
-                    $.each(response["results"], function (index, cluster) {
-                        var option = $("<option></option>").attr("value", cluster.id).text(cluster.name);
-                        cluster_list.append(option);
-                    });
-                }
-            });
-        }
-    });
-
-});
-</script>
-{% endblock %}

+ 24 - 9
netbox/tenancy/forms.py

@@ -4,7 +4,8 @@ from taggit.forms import TagField
 
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from utilities.forms import (
-    APISelect, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, FilterChoiceField, SlugField,
+    APISelect, APISelectMultiple, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField,
+    FilterChoiceField, SlugField,
 )
 from .models import Tenant, TenantGroup
 
@@ -50,6 +51,11 @@ class TenantForm(BootstrapMixin, CustomFieldForm):
         fields = [
             'name', 'slug', 'group', 'description', 'comments', 'tags',
         ]
+        widgets = {
+            'group': APISelect(
+                api_url="/api/tenancy/tenant-groups/"
+            )
+        }
 
 
 class TenantCSVForm(forms.ModelForm):
@@ -80,7 +86,10 @@ class TenantBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
     )
     group = forms.ModelChoiceField(
         queryset=TenantGroup.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/tenancy/tenant-groups/"
+        )
     )
 
     class Meta:
@@ -96,11 +105,14 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
         label='Search'
     )
     group = FilterChoiceField(
-        queryset=TenantGroup.objects.annotate(
-            filter_count=Count('tenants')
-        ),
+        queryset=TenantGroup.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenant-groups/",
+            value_field="slug",
+            null_option=True,
+        )
     )
 
 
@@ -112,9 +124,12 @@ class TenancyForm(ChainedFieldsMixin, forms.Form):
     tenant_group = forms.ModelChoiceField(
         queryset=TenantGroup.objects.all(),
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url="/api/tenancy/tenant-groups/",
+            filter_for={
+                'tenant': 'group_id',
+            },
             attrs={
-                'filter-for': 'tenant',
                 'nullable': 'true',
             }
         )
@@ -126,7 +141,7 @@ class TenancyForm(ChainedFieldsMixin, forms.Form):
         ),
         required=False,
         widget=APISelect(
-            api_url='/api/tenancy/tenants/?group_id={{tenant_group}}'
+            api_url='/api/tenancy/tenants/'
         )
     )
 

+ 102 - 83
netbox/utilities/forms.py

@@ -169,6 +169,7 @@ class ColorSelect(forms.Select):
     def __init__(self, *args, **kwargs):
         kwargs['choices'] = add_blank_choice(COLOR_CHOICES)
         super().__init__(*args, **kwargs)
+        self.attrs['class'] = 'netbox-select2-color-picker'
 
 
 class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
@@ -185,6 +186,7 @@ class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
             ('2', 'Yes'),
             ('3', 'No'),
         )
+        self.attrs['class'] = 'netbox-select2-static'
 
 
 class SelectWithDisabled(forms.Select):
@@ -195,7 +197,42 @@ class SelectWithDisabled(forms.Select):
     option_template_name = 'widgets/selectwithdisabled_option.html'
 
 
-class SelectWithPK(forms.Select):
+class StaticSelect2(SelectWithDisabled):
+    """
+    A static content using the Select2 widget
+
+    :param filter_for: (Optional) A dict of chained form fields for which this field is a filter. The key is the
+        name of the filter-for field (child field) and the value is the name of the query param filter.
+    """
+
+    def __init__(self, filter_for=None, *args, **kwargs):
+
+        super().__init__(*args, **kwargs)
+
+        self.attrs['class'] = 'netbox-select2-static'
+        if filter_for:
+            for key, value in filter_for.items():
+                self.add_filter_for(key, value)
+
+    def add_filter_for(self, name, value):
+        """
+        Add details for an additional query param in the form of a data-filter-for-* attribute.
+
+        :param name: The name of the query param
+        :param value: The value of the query param
+        """
+        self.attrs['data-filter-for-{}'.format(name)] = value
+
+
+class StaticSelect2Multiple(StaticSelect2, forms.SelectMultiple):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.attrs['data-multiple'] = 1
+
+
+class SelectWithPK(StaticSelect2):
     """
     Include the primary key of each option in the option label (e.g. "Router7 (4721)").
     """
@@ -237,62 +274,91 @@ class APISelect(SelectWithDisabled):
 
     :param api_url: API URL
     :param display_field: (Optional) Field to display for child in selection list. Defaults to `name`.
+    :param value_field: (Optional) Field to use for the option value in selection list. Defaults to `id`.
     :param disabled_indicator: (Optional) Mark option as disabled if this field equates true.
-    :param url_conditional_append: (Optional) A dict of URL query strings to append to the URL if the
+    :param filter_for: (Optional) A dict of chained form fields for which this field is a filter. The key is the
+        name of the filter-for field (child field) and the value is the name of the query param filter.
+    :param conditional_query_params: (Optional) A dict of URL query params to append to the URL if the
         condition is met. The condition is the dict key and is specified in the form `<field_name>__<field_value>`.
-        If the provided field value is selected for the given field, the URL query string will be appended to
-        the rendered URL. This is useful in cases where a particular field value dictates an additional API filter.
+        If the provided field value is selected for the given field, the URL query param will be appended to
+        the rendered URL. The value is the in the from `<param_name>=<param_value>`. This is useful in cases where
+        a particular field value dictates an additional API filter.
+    :param additional_query_params: Optional) A dict of query params to append to the API request. The key is the
+        name of the query param and the value if the query param's value.
+    :param null_option: If true, include the static null option in the selection list.
     """
 
     def __init__(
         self,
         api_url,
         display_field=None,
+        value_field=None,
         disabled_indicator=None,
-        url_conditional_append=None,
+        filter_for=None,
+        conditional_query_params=None,
+        additional_query_params=None,
+        null_option=False,
         *args,
         **kwargs
     ):
 
         super().__init__(*args, **kwargs)
 
-        self.attrs['class'] = 'api-select'
-        self.attrs['api-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/'))  # Inject BASE_PATH
+        self.attrs['class'] = 'netbox-select2-api'
+        self.attrs['data-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/'))  # Inject BASE_PATH
         if display_field:
             self.attrs['display-field'] = display_field
+        if value_field:
+            self.attrs['value-field'] = value_field
         if disabled_indicator:
             self.attrs['disabled-indicator'] = disabled_indicator
-        if url_conditional_append:
-            for key, value in url_conditional_append.items():
-                self.attrs["data-url-conditional-append-{}".format(key)] = value
-
-
-class APISelectMultiple(APISelect):
-    allow_multiple_selected = True
-
-
-class Livesearch(forms.TextInput):
-    """
-    A text widget that carries a few extra bits of data for use in AJAX-powered autocomplete search
-
-    :param query_key: The name of the parameter to query against
-    :param query_url: The name of the API URL to query
-    :param field_to_update: The name of the "real" form field whose value is being set
-    :param obj_label: The field to use as the option label (optional)
-    """
-
-    def __init__(self, query_key, query_url, field_to_update, obj_label=None, *args, **kwargs):
+        if filter_for:
+            for key, value in filter_for.items():
+                self.add_filter_for(key, value)
+        if conditional_query_params:
+            for key, value in conditional_query_params.items():
+                self.add_conditional_query_param(key, value)
+        if additional_query_params:
+            for key, value in additional_query_params.items():
+                self.add_additional_query_param(key, value)
+        if null_option:
+            self.attrs['data-null-option'] = 1
+
+    def add_filter_for(self, name, value):
+        """
+        Add details for an additional query param in the form of a data-filter-for-* attribute.
+
+        :param name: The name of the query param
+        :param value: The value of the query param
+        """
+        self.attrs['data-filter-for-{}'.format(name)] = value
+
+    def add_additional_query_param(self, name, value):
+        """
+        Add details for an additional query param in the form of a data-* attribute.
+
+        :param name: The name of the query param
+        :param value: The value of the query param
+        """
+        self.attrs['data-additional-query-param-{}'.format(name)] = value
+
+    def add_conditional_query_param(self, condition, value):
+        """
+        Add details for a URL query strings to append to the URL if the condition is met.
+        The condition is specified in the form `<field_name>__<field_value>`.
+
+        :param condition: The condition for the query param
+        :param value: The value of the query param
+        """
+        self.attrs['data-conditional-query-param-{}'.format(condition)] = value
+
+
+class APISelectMultiple(APISelect, forms.SelectMultiple):
 
+    def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        self.attrs = {
-            'data-key': query_key,
-            'data-source': reverse_lazy(query_url),
-            'data-field': field_to_update,
-        }
-
-        if obj_label:
-            self.attrs['data-label'] = obj_label
+        self.attrs['data-multiple'] = 1
 
 
 #
@@ -530,53 +596,6 @@ class FilterTreeNodeMultipleChoiceField(FilterChoiceFieldMixin, TreeNodeMultiple
     pass
 
 
-class AnnotatedMultipleChoiceField(forms.MultipleChoiceField):
-    """
-    Render a set of static choices with each choice annotated to include a count of related objects. For example, this
-    field can be used to display a list of all available device statuses along with the number of devices currently
-    assigned to each status.
-    """
-
-    def annotate_choices(self):
-
-        # Aggregate objects by choice field values
-        queryset = self.annotate.values(
-            self.annotate_field
-        ).annotate(
-            count=Count(self.annotate_field)
-        ).order_by(
-            self.annotate_field
-        )
-        choice_counts = {
-            c[self.annotate_field]: c['count'] for c in queryset
-        }
-
-        annotated_choices = []
-
-        # Optionally add a "none" choice
-        if self.include_null:
-            annotated_choices.append((
-                settings.FILTERS_NULL_CHOICE_VALUE,
-                '-- {} --'.format(settings.FILTERS_NULL_CHOICE_LABEL)
-            ))
-
-        # Append each choice and its annotated count
-        for c in self.static_choices:
-            annotated_choices.append(
-                (c[0], '{} ({})'.format(c[1], choice_counts.get(c[0], 0)))
-            )
-
-        return annotated_choices
-
-    def __init__(self, choices, annotate, annotate_field, include_null=False, **kwargs):
-        self.annotate = annotate
-        self.annotate_field = annotate_field
-        self.include_null = include_null
-        self.static_choices = unpack_grouped_choices(choices)
-
-        super().__init__(choices=self.annotate_choices, **kwargs)
-
-
 class LaxURLField(forms.URLField):
     """
     Modifies Django's built-in URLField in two ways:
@@ -642,7 +661,7 @@ class ChainedFieldsMixin(forms.BaseForm):
 
                 filters_dict = {}
                 for (db_field, parent_field) in field.chains:
-                    if self.is_bound and parent_field in self.data:
+                    if self.is_bound and parent_field in self.data and self.data[parent_field]:
                         filters_dict[db_field] = self.data[parent_field] or None
                     elif self.initial.get(parent_field):
                         filters_dict[db_field] = self.initial[parent_field]

+ 1 - 1
netbox/utilities/templates/widgets/colorselect_option.html

@@ -1 +1 @@
-<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %} style="background-color: #{{ widget.value }}">{{ widget.label }}</option>
+<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %} class="color-selection-{{ widget.value }}">{{ widget.label }}</option>

+ 157 - 63
netbox/virtualization/forms.py

@@ -1,7 +1,5 @@
 from django import forms
 from django.core.exceptions import ValidationError
-from django.db.models import Count
-from mptt.forms import TreeNodeChoiceField
 from taggit.forms import TagField
 
 from dcim.constants import IFACE_FF_VIRTUAL, IFACE_MODE_ACCESS, IFACE_MODE_TAGGED_ALL
@@ -12,10 +10,10 @@ from ipam.models import IPAddress
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
 from utilities.forms import (
-    AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
+    add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
-    ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField,
-    JSONField, SlugField, SmallTextarea, add_blank_choice,
+    ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField,
+    SmallTextarea, StaticSelect2, StaticSelect2Multiple
 )
 from .constants import VM_STATUS_CHOICES
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@@ -92,6 +90,17 @@ class ClusterForm(BootstrapMixin, CustomFieldForm):
         fields = [
             'name', 'type', 'group', 'site', 'comments', 'tags',
         ]
+        widgets = {
+            'type': APISelect(
+                api_url="/api/virtualization/cluster-types/"
+            ),
+            'group': APISelect(
+                api_url="/api/virtualization/cluster-groups/"
+            ),
+            'site': APISelect(
+                api_url="/api/dcim/sites/"
+            ),
+        }
 
 
 class ClusterCSVForm(forms.ModelForm):
@@ -134,15 +143,24 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
     )
     type = forms.ModelChoiceField(
         queryset=ClusterType.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/virtualization/cluster-types/"
+        )
     )
     group = forms.ModelChoiceField(
         queryset=ClusterGroup.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/virtualization/cluster-groups/"
+        )
     )
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/dcim/sites/"
+        )
     )
     comments = CommentField(
         widget=SmallTextarea()
@@ -158,37 +176,48 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Cluster
     q = forms.CharField(required=False, label='Search')
     type = FilterChoiceField(
-        queryset=ClusterType.objects.annotate(
-            filter_count=Count('clusters')
-        ),
+        queryset=ClusterType.objects.all(),
         to_field_name='slug',
         required=False,
+        widget=APISelectMultiple(
+            api_url="/api/virtualization/cluster-types/",
+            value_field='slug',
+        )
     )
     group = FilterChoiceField(
-        queryset=ClusterGroup.objects.annotate(
-            filter_count=Count('clusters')
-        ),
+        queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
         null_label='-- None --',
         required=False,
+        widget=APISelectMultiple(
+            api_url="/api/virtualization/cluster-groups/",
+            value_field='slug',
+            null_option=True,
+        )
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(
-            filter_count=Count('clusters')
-        ),
+        queryset=Site.objects.all(),
         to_field_name='slug',
         null_label='-- None --',
         required=False,
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field='slug',
+            null_option=True,
+        )
     )
 
 
 class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
-    region = TreeNodeChoiceField(
+    region = forms.ModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url="/api/dcim/regions/",
+            filter_for={
+                "site": "region_id",
+            },
             attrs={
-                'filter-for': 'site',
                 'nullable': 'true',
             }
         )
@@ -200,9 +229,10 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
         ),
         required=False,
         widget=APISelect(
-            api_url='/api/dcim/sites/?region_id={{region}}',
-            attrs={
-                'filter-for': 'rack',
+            api_url='/api/dcim/sites/',
+            filter_for={
+                "rack": "site_id",
+                "devices": "site_id",
             }
         )
     )
@@ -213,9 +243,11 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
         ),
         required=False,
         widget=APISelect(
-            api_url='/api/dcim/racks/?site_id={{site}}',
+            api_url='/api/dcim/racks/',
+            filter_for={
+                "devices": "rack_id"
+            },
             attrs={
-                'filter-for': 'devices',
                 'nullable': 'true',
             }
         )
@@ -227,7 +259,7 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
             ('rack', 'rack'),
         ),
         widget=APISelectMultiple(
-            api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
+            api_url='/api/dcim/devices/',
             display_field='display_name',
             disabled_indicator='cluster'
         )
@@ -275,9 +307,12 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     cluster_group = forms.ModelChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url='/api/virtualization/cluster-groups/',
+            filter_for={
+                "cluster": "group_id",
+            },
             attrs={
-                'filter-for': 'cluster',
                 'nullable': 'true',
             }
         )
@@ -288,7 +323,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             ('group', 'cluster_group'),
         ),
         widget=APISelect(
-            api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'
+            api_url='/api/virtualization/clusters/'
         )
     )
     tags = TagField(
@@ -308,6 +343,20 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
                                   "config context",
         }
+        widgets = {
+            "status": StaticSelect2(),
+            "role": APISelect(
+                api_url="/api/dcim/device-roles/",
+                additional_query_params={
+                    "vm_role": "true"
+                }
+            ),
+            'primary_ip4': StaticSelect2(),
+            'primary_ip6': StaticSelect2(),
+            'platform': APISelect(
+                api_url='/api/dcim/platforms/'
+            )
+        }
 
     def __init__(self, *args, **kwargs):
 
@@ -413,25 +462,41 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
     status = forms.ChoiceField(
         choices=add_blank_choice(VM_STATUS_CHOICES),
         required=False,
-        initial=''
+        initial='',
+        widget=StaticSelect2(),
     )
     cluster = forms.ModelChoiceField(
         queryset=Cluster.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url='/api/virtualization/clusters/'
+        )
     )
     role = forms.ModelChoiceField(
         queryset=DeviceRole.objects.filter(
             vm_role=True
         ),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url="/api/dcim/device-roles/",
+            additional_query_params={
+                "vm_role": "true"
+            }
+        )
     )
     tenant = forms.ModelChoiceField(
         queryset=Tenant.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url='/api/tenancy/tenants/'
+        )
     )
     platform = forms.ModelChoiceField(
         queryset=Platform.objects.all(),
-        required=False
+        required=False,
+        widget=APISelect(
+            api_url='/api/dcim/platforms/'
+        )
     )
     vcpus = forms.IntegerField(
         required=False,
@@ -464,59 +529,87 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
     cluster_group = FilterChoiceField(
         queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url='/api/virtualization/cluster-groups/',
+            value_field="slug",
+            null_option=True,
+        )
     )
     cluster_type = FilterChoiceField(
         queryset=ClusterType.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url='/api/virtualization/cluster-types/',
+            value_field="slug",
+            null_option=True,
+        )
     )
     cluster_id = FilterChoiceField(
-        queryset=Cluster.objects.annotate(
-            filter_count=Count('virtual_machines')
-        ),
-        label='Cluster'
+        queryset=Cluster.objects.all(),
+        label='Cluster',
+        widget=APISelectMultiple(
+            api_url='/api/virtualization/clusters/',
+        )
     )
-    region = FilterTreeNodeMultipleChoiceField(
+    region = FilterChoiceField(
         queryset=Region.objects.all(),
         to_field_name='slug',
         required=False,
+        widget=APISelectMultiple(
+            api_url='/api/dcim/regions/',
+            value_field="slug",
+            null_option=True,
+        )
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(
-            filter_count=Count('clusters__virtual_machines')
-        ),
+        queryset=Site.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url='/api/dcim/sites/',
+            value_field="slug",
+            null_option=True,
+        )
     )
     role = FilterChoiceField(
-        queryset=DeviceRole.objects.filter(
-            vm_role=True
-        ).annotate(
-            filter_count=Count('virtual_machines')
-        ),
+        queryset=DeviceRole.objects.filter(vm_role=True),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url='/api/dcim/device-roles/',
+            value_field="slug",
+            null_option=True,
+            additional_query_params={
+                'vm_role': 'true'
+            }
+        )
     )
-    status = AnnotatedMultipleChoiceField(
+    status = forms.MultipleChoiceField(
         choices=VM_STATUS_CHOICES,
-        annotate=VirtualMachine.objects.all(),
-        annotate_field='status',
-        required=False
+        required=False,
+        widget=StaticSelect2Multiple()
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(
-            filter_count=Count('virtual_machines')
-        ),
+        queryset=Tenant.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url='/api/tenancy/tenants/',
+            value_field="slug",
+            null_option=True,
+        )
     )
     platform = FilterChoiceField(
-        queryset=Platform.objects.annotate(
-            filter_count=Count('virtual_machines')
-        ),
+        queryset=Platform.objects.all(),
         to_field_name='slug',
-        null_label='-- None --'
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url='/api/dcim/platforms/',
+            value_field="slug",
+            null_option=True,
+        )
     )
 
 
@@ -538,6 +631,7 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
         widgets = {
             'virtual_machine': forms.HiddenInput(),
             'form_factor': forms.HiddenInput(),
+            'mode': StaticSelect2()
         }
         labels = {
             'mode': '802.1Q Mode',

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů