nautobot 2.4.2__py3-none-any.whl → 2.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. nautobot/apps/filters.py +2 -0
  2. nautobot/circuits/filters.py +1 -1
  3. nautobot/circuits/templates/circuits/inc/circuit_termination.html +1 -1
  4. nautobot/circuits/tests/integration/test_circuit.py +135 -0
  5. nautobot/circuits/tests/test_models.py +5 -3
  6. nautobot/circuits/views.py +4 -1
  7. nautobot/cloud/api/views.py +3 -3
  8. nautobot/cloud/filters.py +3 -6
  9. nautobot/cloud/tests/test_filters.py +21 -0
  10. nautobot/core/admin.py +2 -0
  11. nautobot/core/constants.py +0 -1
  12. nautobot/core/forms/__init__.py +2 -0
  13. nautobot/core/forms/forms.py +2 -1
  14. nautobot/core/forms/widgets.py +8 -0
  15. nautobot/core/jobs/__init__.py +2 -1
  16. nautobot/core/management/commands/generate_performance_test_endpoints.py +271 -0
  17. nautobot/core/models/utils.py +6 -1
  18. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  19. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  20. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  21. nautobot/core/templates/generic/object_create.html +5 -0
  22. nautobot/core/templates/generic/object_delete.html +1 -1
  23. nautobot/core/templates/generic/object_detail.html +1 -1
  24. nautobot/core/templates/generic/object_edit.html +1 -1
  25. nautobot/core/templates/inc/javascript.html +3 -0
  26. nautobot/core/templates/widgets/clearable_file.html +5 -0
  27. nautobot/core/templatetags/helpers.py +3 -3
  28. nautobot/core/templatetags/ui_framework.py +20 -4
  29. nautobot/core/testing/forms.py +1 -1
  30. nautobot/core/testing/integration.py +37 -7
  31. nautobot/core/tests/test_api.py +1 -1
  32. nautobot/core/tests/test_commands.py +31 -0
  33. nautobot/core/tests/test_graphql.py +3 -3
  34. nautobot/core/tests/test_jobs.py +4 -1
  35. nautobot/core/tests/test_utils.py +17 -2
  36. nautobot/core/ui/object_detail.py +1 -1
  37. nautobot/core/utils/lookup.py +12 -1
  38. nautobot/core/views/generic.py +9 -1
  39. nautobot/core/views/mixins.py +9 -1
  40. nautobot/dcim/api/serializers.py +36 -0
  41. nautobot/dcim/api/views.py +12 -11
  42. nautobot/dcim/elevations.py +17 -4
  43. nautobot/dcim/factory.py +9 -1
  44. nautobot/dcim/filters/__init__.py +27 -1
  45. nautobot/dcim/forms.py +16 -7
  46. nautobot/dcim/models/devices.py +12 -7
  47. nautobot/dcim/signals.py +26 -0
  48. nautobot/dcim/templates/dcim/cable_trace.html +4 -4
  49. nautobot/dcim/templates/dcim/consoleport.html +14 -4
  50. nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
  51. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
  52. nautobot/dcim/templates/dcim/frontport.html +7 -2
  53. nautobot/dcim/templates/dcim/interface.html +9 -4
  54. nautobot/dcim/templates/dcim/powerfeed.html +8 -3
  55. nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
  56. nautobot/dcim/templates/dcim/powerport.html +14 -4
  57. nautobot/dcim/templates/dcim/rearport.html +7 -2
  58. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -62
  59. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +6 -0
  60. nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
  61. nautobot/dcim/tests/test_api.py +176 -0
  62. nautobot/dcim/tests/test_filters.py +56 -3
  63. nautobot/dcim/tests/test_models.py +41 -1
  64. nautobot/dcim/views.py +24 -14
  65. nautobot/extras/api/mixins.py +1 -1
  66. nautobot/extras/api/views.py +4 -4
  67. nautobot/extras/filters/__init__.py +4 -0
  68. nautobot/extras/forms/forms.py +4 -0
  69. nautobot/extras/jobs.py +8 -1
  70. nautobot/extras/models/datasources.py +7 -3
  71. nautobot/extras/plugins/__init__.py +26 -1
  72. nautobot/extras/templates/extras/inc/jobresult.html +12 -13
  73. nautobot/extras/templates/extras/job.html +1 -0
  74. nautobot/extras/templates/extras/objectchange.html +28 -12
  75. nautobot/extras/tests/test_api.py +16 -15
  76. nautobot/extras/tests/test_dynamicgroups.py +14 -0
  77. nautobot/extras/tests/test_filters.py +2 -0
  78. nautobot/extras/tests/test_plugins.py +32 -1
  79. nautobot/extras/tests/test_views.py +209 -11
  80. nautobot/extras/utils.py +30 -0
  81. nautobot/extras/views.py +32 -14
  82. nautobot/ipam/api/serializers.py +7 -8
  83. nautobot/ipam/api/views.py +5 -5
  84. nautobot/ipam/factory.py +27 -8
  85. nautobot/ipam/filters.py +67 -29
  86. nautobot/ipam/formfields.py +51 -0
  87. nautobot/ipam/forms.py +15 -7
  88. nautobot/ipam/migrations/0051_added_optional_vrf_relationship_to_vdc.py +41 -0
  89. nautobot/ipam/models.py +63 -5
  90. nautobot/ipam/tables.py +21 -7
  91. nautobot/ipam/tests/test_api.py +107 -66
  92. nautobot/ipam/tests/test_filters.py +145 -5
  93. nautobot/ipam/tests/test_views.py +15 -2
  94. nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
  95. nautobot/project-static/css/base.css +11 -0
  96. nautobot/project-static/css/dark.css +2 -1
  97. nautobot/project-static/docs/apps/index.html +1 -1
  98. nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
  99. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +62 -0
  100. nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -3
  101. nautobot/project-static/docs/development/apps/api/models/graphql.html +9 -13
  102. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +94 -1
  103. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -5
  104. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -3
  105. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -3
  106. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -3
  107. nautobot/project-static/docs/development/apps/api/prometheus.html +0 -3
  108. nautobot/project-static/docs/development/apps/api/setup.html +1 -1
  109. nautobot/project-static/docs/development/apps/api/testing.html +0 -6
  110. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -3
  111. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -3
  112. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -3
  113. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -3
  114. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -7
  115. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -7
  116. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -4
  117. nautobot/project-static/docs/development/apps/api/views/notes.html +0 -3
  118. nautobot/project-static/docs/development/apps/index.html +2 -35
  119. nautobot/project-static/docs/development/apps/migration/code-updates.html +7 -6
  120. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
  121. nautobot/project-static/docs/development/apps/migration/from-v1.html +3 -3
  122. nautobot/project-static/docs/development/core/application-registry.html +0 -6
  123. nautobot/project-static/docs/development/core/best-practices.html +1 -28
  124. nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
  125. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +65 -11
  126. nautobot/project-static/docs/development/core/getting-started.html +14 -18
  127. nautobot/project-static/docs/development/core/homepage.html +0 -3
  128. nautobot/project-static/docs/development/core/index.html +1 -1
  129. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +3 -3
  130. nautobot/project-static/docs/development/core/model-checklist.html +1 -1
  131. nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
  132. nautobot/project-static/docs/development/core/release-checklist.html +1 -1
  133. nautobot/project-static/docs/development/core/settings.html +1 -1
  134. nautobot/project-static/docs/development/core/style-guide.html +4 -9
  135. nautobot/project-static/docs/development/core/templates.html +0 -3
  136. nautobot/project-static/docs/development/core/testing.html +0 -9
  137. nautobot/project-static/docs/development/jobs/index.html +11 -30
  138. nautobot/project-static/docs/development/jobs/migration/from-v1.html +3 -2
  139. nautobot/project-static/docs/index.html +3 -2
  140. nautobot/project-static/docs/objects.inv +0 -0
  141. nautobot/project-static/docs/overview/application_stack.html +2 -20
  142. nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
  143. nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
  144. nautobot/project-static/docs/release-notes/version-1.2.html +3 -3
  145. nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
  146. nautobot/project-static/docs/release-notes/version-1.4.html +17 -17
  147. nautobot/project-static/docs/release-notes/version-1.5.html +8 -8
  148. nautobot/project-static/docs/release-notes/version-1.6.html +4 -4
  149. nautobot/project-static/docs/release-notes/version-2.0.html +10 -10
  150. nautobot/project-static/docs/release-notes/version-2.1.html +7 -7
  151. nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
  152. nautobot/project-static/docs/release-notes/version-2.3.html +4 -4
  153. nautobot/project-static/docs/release-notes/version-2.4.html +379 -0
  154. nautobot/project-static/docs/requirements.txt +1 -1
  155. nautobot/project-static/docs/search/search_index.json +1 -1
  156. nautobot/project-static/docs/sitemap.xml +290 -290
  157. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  158. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +3 -3
  159. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +4 -4
  160. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
  161. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +3 -13
  162. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +5 -5
  163. nautobot/project-static/docs/user-guide/administration/guides/docker.html +3 -18
  164. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
  165. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +4 -4
  166. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +15 -15
  167. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
  168. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
  169. nautobot/project-static/docs/user-guide/administration/installation/index.html +0 -16
  170. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
  171. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +7 -10
  172. nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -12
  173. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
  174. nautobot/project-static/docs/user-guide/administration/security/index.html +1 -1
  175. nautobot/project-static/docs/user-guide/administration/security/notices.html +1 -0
  176. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +5 -35
  177. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +12 -9
  180. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +0 -4
  181. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -3
  182. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -4
  183. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -4
  184. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -4
  185. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -4
  186. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -4
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -4
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -3
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -4
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -4
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -4
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -17
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -3
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -4
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -4
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -3
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -7
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -4
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -4
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -4
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -4
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -4
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -4
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -4
  205. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -6
  206. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -3
  207. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -4
  208. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -4
  209. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -8
  210. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +15 -15
  211. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
  212. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -6
  213. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -3
  214. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -15
  215. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -27
  216. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -8
  217. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +3 -6
  218. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -8
  219. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -7
  220. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -3
  221. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -3
  222. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +2 -2
  223. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +6 -6
  224. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -14
  225. nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -3
  226. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -10
  227. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -3
  228. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -14
  229. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -19
  230. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -9
  231. nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -8
  232. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -4
  233. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -13
  234. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +0 -5
  235. nautobot/project-static/js/dropdown.js +28 -0
  236. nautobot/project-static/js/editor.js +292 -0
  237. nautobot/project-static/monaco-editor-0.52.2/README.md +81 -0
  238. nautobot/project-static/monaco-editor-0.52.2/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  239. nautobot/project-static/monaco-editor-0.52.2/vs/base/worker/workerMain.js +31 -0
  240. nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/xml/xml.js +10 -0
  241. nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/yaml/yaml.js +10 -0
  242. nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.css +8 -0
  243. nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.js +798 -0
  244. nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonMode.js +19 -0
  245. nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonWorker.js +42 -0
  246. nautobot/project-static/monaco-editor-0.52.2/vs/loader.js +11 -0
  247. nautobot/tenancy/filters/__init__.py +3 -5
  248. nautobot/tenancy/forms.py +9 -0
  249. nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
  250. nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
  251. nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
  252. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
  253. nautobot/tenancy/tests/test_filters.py +10 -0
  254. nautobot/tenancy/tests/test_views.py +5 -1
  255. nautobot/tenancy/urls.py +7 -79
  256. nautobot/tenancy/views.py +51 -80
  257. nautobot/virtualization/views.py +0 -1
  258. nautobot/wireless/api/serializers.py +6 -1
  259. nautobot/wireless/api/views.py +3 -3
  260. nautobot/wireless/tables.py +9 -4
  261. nautobot/wireless/tests/test_api.py +5 -9
  262. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/METADATA +9 -9
  263. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/RECORD +267 -246
  264. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/LICENSE.txt +0 -0
  265. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/NOTICE +0 -0
  266. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/WHEEL +0 -0
  267. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/entry_points.txt +0 -0
nautobot/apps/filters.py CHANGED
@@ -43,6 +43,7 @@ from nautobot.extras.filters.mixins import (
43
43
  StatusFilter,
44
44
  )
45
45
  from nautobot.extras.plugins import FilterExtension
46
+ from nautobot.ipam.filters import PrefixFilter
46
47
  from nautobot.tenancy.filters import TenancyModelFilterSetMixin
47
48
 
48
49
  __all__ = (
@@ -72,6 +73,7 @@ __all__ = (
72
73
  "NaturalKeyOrPKMultipleChoiceFilter",
73
74
  "NautobotFilterSet",
74
75
  "NumericArrayFilter",
76
+ "PrefixFilter",
75
77
  "RelatedMembershipBooleanFilter",
76
78
  "RelationshipFilter",
77
79
  "RelationshipModelFilterSetMixin",
@@ -16,7 +16,7 @@ from nautobot.dcim.filters import (
16
16
  )
17
17
  from nautobot.dcim.models import Location
18
18
  from nautobot.extras.filters import NautobotFilterSet, StatusModelFilterSetMixin
19
- from nautobot.tenancy.filters import TenancyModelFilterSetMixin
19
+ from nautobot.tenancy.filters.mixins import TenancyModelFilterSetMixin
20
20
 
21
21
  from .models import Circuit, CircuitTermination, CircuitType, Provider, ProviderNetwork
22
22
 
@@ -6,7 +6,7 @@ In a future release, we should delete this file.
6
6
 
7
7
  <div class="panel panel-default">
8
8
  <div class="panel-heading">
9
- {% include 'circuits/inc/circuit_termination_header_extra_content.html' with termination=termination %}
9
+ {% include 'circuits/inc/circuit_termination_header_extra_content.html' with termination=termination side=side %}
10
10
  <strong>Termination - {{ side }} Side</strong>
11
11
  </div>
12
12
  {% if termination %}
@@ -0,0 +1,135 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.urls import reverse
3
+
4
+ from nautobot.circuits.models import Circuit, CircuitTermination, CircuitType, Provider
5
+ from nautobot.core.testing.integration import ObjectDetailsMixin, ObjectsListMixin, SeleniumTestCase
6
+ from nautobot.dcim.models import Location, LocationType
7
+ from nautobot.extras.models import Status
8
+ from nautobot.tenancy.models import Tenant, TenantGroup
9
+
10
+
11
+ class CircuitTestCase(SeleniumTestCase, ObjectsListMixin, ObjectDetailsMixin):
12
+ """
13
+ Integration test to check circuits related test cases.
14
+ """
15
+
16
+ def setUp(self):
17
+ super().setUp()
18
+ self.login_as_superuser()
19
+
20
+ # Termination requirements
21
+ location_type, location_created = LocationType.objects.get_or_create(name="Circuit at Home")
22
+ if location_created:
23
+ location_ct = ContentType.objects.get_for_model(CircuitTermination)
24
+ location_type.content_types.set([location_ct])
25
+ location_type.save()
26
+
27
+ location_status = Status.objects.get_for_model(Location).first()
28
+ self.location, _ = Location.objects.get_or_create(
29
+ name="A Test Location",
30
+ status=location_status,
31
+ location_type=location_type,
32
+ )
33
+
34
+ self.provider, _ = Provider.objects.get_or_create(name="World Best Cables")
35
+ self.circuit_type, _ = CircuitType.objects.get_or_create(
36
+ name="Yellow Cable",
37
+ )
38
+ self.tenant_group, _ = TenantGroup.objects.get_or_create(name="Family Inc.")
39
+ self.tenant, _ = Tenant.objects.get_or_create(name="Tenant 1", tenant_group=self.tenant_group)
40
+
41
+ def create_circuit(self, name):
42
+ circuit, _ = Circuit.objects.get_or_create(
43
+ provider=self.provider,
44
+ cid=name,
45
+ circuit_type=self.circuit_type,
46
+ status=Status.objects.get_for_model(Circuit).first(),
47
+ )
48
+ return circuit
49
+
50
+ def test_circuit_create(self):
51
+ cid = "Circuit-test-abc123"
52
+ description = "My Precious Circuit"
53
+
54
+ # Create new Circuit from list view
55
+ self.click_navbar_entry("Circuits", "Circuits")
56
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("circuits:circuit_list"))
57
+
58
+ self.click_list_view_add_button()
59
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("circuits:circuit_add"))
60
+
61
+ # Fill Circuit creation form
62
+ self.fill_select2_field("provider", self.provider.name)
63
+ self.browser.fill("cid", cid)
64
+ self.fill_select2_field("circuit_type", self.circuit_type.name)
65
+ self.fill_select2_field("status", "") # pick first status
66
+ self.browser.fill("install_date", "2025-01-01")
67
+ self.browser.fill("commit_rate", 192)
68
+ self.browser.fill("description", "My Precious Circuit")
69
+ self.fill_select2_field("tenant_group", "Family Inc.")
70
+ self.fill_select2_field("tenant", "Tenant 1")
71
+
72
+ self.click_edit_form_create_button()
73
+ self.assertTrue(self.browser.is_text_present("Created circuit Circuit-test-abc123"))
74
+
75
+ # Navigate to Circuit details by filtering the one just created and clicking on it
76
+ self.click_navbar_entry("Circuits", "Circuits")
77
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("circuits:circuit_list"))
78
+
79
+ self.apply_filter("circuit_type", "Yellow Cable")
80
+ self.assertEqual(self.objects_list_visible_items, 1)
81
+
82
+ self.click_table_link()
83
+
84
+ # Assert that value are properly set
85
+ circuit = Circuit.objects.get(cid=cid)
86
+ self.assertIn(self.live_server_url + reverse("circuits:circuit", kwargs={"pk": circuit.pk}), self.browser.url)
87
+
88
+ self.assertPanelValue("Circuit", "Circuit ID", cid)
89
+ self.assertPanelValue("Circuit", "Status", "Active")
90
+ self.assertPanelValue("Circuit", "Provider", self.provider.name)
91
+ self.assertPanelValue("Circuit", "Circuit Type", self.circuit_type.name)
92
+ self.assertPanelValue("Circuit", "Tenant", self.tenant.name)
93
+ self.assertPanelValue("Circuit", "Date Installed", "Jan. 1, 2025")
94
+ self.assertPanelValue("Circuit", "Commit Rate (Kbps)", "192")
95
+ self.assertPanelValue("Circuit", "Description", description)
96
+
97
+ def test_circuit_create_termination(self):
98
+ circuit = self.create_circuit("Circuit-test-termination")
99
+ sides = ["A", "Z"]
100
+ for side in sides:
101
+ with self.subTest(side=side):
102
+ # Go to Circuit details page
103
+ details_url = self.live_server_url + reverse("circuits:circuit", kwargs={"pk": circuit.pk})
104
+ self.browser.visit(details_url)
105
+ self.assertIn(details_url, self.browser.url)
106
+
107
+ # Find and click add termination button
108
+ termination_panel_xpath = f'//*[@id="main"]//div[@class="panel-heading"][contains(normalize-space(), "Termination - {side} Side")]'
109
+ self.browser.find_by_xpath(f'{termination_panel_xpath}//a[normalize-space()="Add"]').click()
110
+
111
+ port_speed = ord(side)
112
+ upstream_speed = ord(side) + 10
113
+ xconnect_id = f"xconnect-id-{side}-123"
114
+ pp_info = f"pp-info-{side}-123"
115
+ description = f"{side}-side"
116
+
117
+ # Fill termination creation form
118
+ self.fill_select2_field("location", self.location.name)
119
+ self.browser.fill("port_speed", port_speed)
120
+ self.browser.fill("upstream_speed", upstream_speed)
121
+ self.browser.fill("xconnect_id", xconnect_id)
122
+ self.browser.fill("pp_info", pp_info)
123
+ self.browser.fill("description", description)
124
+ self.click_edit_form_create_button()
125
+
126
+ self.assertTrue(self.browser.is_text_present(f"Created circuit termination Termination {side}"))
127
+
128
+ # Assert that value are properly set
129
+ panel_label = f"Termination - {side} Side"
130
+ self.assertPanelValue(panel_label, "Location", self.location.name)
131
+ self.assertPanelValue(panel_label, "Port Speed (Kbps)", port_speed)
132
+ self.assertPanelValue(panel_label, "Upstream Speed (Kbps)", upstream_speed)
133
+ self.assertPanelValue(panel_label, "Cross-connect ID", xconnect_id)
134
+ self.assertPanelValue(panel_label, "Patch Panel/port(s)", pp_info)
135
+ self.assertPanelValue(panel_label, "Description", description)
@@ -17,8 +17,7 @@ class CircuitTerminationModelTestCase(ModelTestCases.BaseModelTestCase):
17
17
  provider = Provider.objects.first()
18
18
  circuit_type = CircuitType.objects.first()
19
19
 
20
- location_type_1 = LocationType.objects.get(name="Campus")
21
- location_type_1.content_types.set([])
20
+ location_type_1 = LocationType.objects.create(name="University")
22
21
  location_type_2 = LocationType.objects.get(name="Building")
23
22
  location_type_2.content_types.add(ContentType.objects.get_for_model(CircuitTermination))
24
23
  status = Status.objects.get_for_model(Circuit).first()
@@ -26,7 +25,10 @@ class CircuitTerminationModelTestCase(ModelTestCases.BaseModelTestCase):
26
25
  cid="Circuit 1", provider=provider, circuit_type=circuit_type, status=status
27
26
  )
28
27
  cls.provider_network = ProviderNetwork.objects.create(name="Provider Network 1", provider=provider)
29
- cls.location_1 = Location.objects.filter(location_type=location_type_1)[0]
28
+ location_status = Status.objects.get_for_model(Location).first()
29
+ cls.location_1 = Location.objects.create(
30
+ name="Department", location_type=location_type_1, status=location_status
31
+ )
30
32
  cls.location_2 = Location.objects.filter(location_type=location_type_2)[0]
31
33
 
32
34
  cloud_resource_type = CloudResourceType.objects.get_for_model(CloudNetwork).first()
@@ -134,6 +134,7 @@ class CircuitUIViewSet(NautobotUIViewSet):
134
134
 
135
135
  class CircuitTerminationPanel(ObjectFieldsPanel):
136
136
  def __init__(self, **kwargs):
137
+ self.side = kwargs.pop("side")
137
138
  super().__init__(
138
139
  fields=(
139
140
  "location", # TODO: render location hierarchy
@@ -161,7 +162,7 @@ class CircuitUIViewSet(NautobotUIViewSet):
161
162
  return True
162
163
 
163
164
  def get_extra_context(self, context):
164
- return {"termination": context[self.context_object_key]}
165
+ return {"termination": context[self.context_object_key], "side": self.side}
165
166
 
166
167
  def get_data(self, context):
167
168
  """
@@ -237,12 +238,14 @@ class CircuitUIViewSet(NautobotUIViewSet):
237
238
  section=SectionChoices.RIGHT_HALF,
238
239
  weight=100,
239
240
  context_object_key="circuit_termination_a",
241
+ side=CircuitTerminationSideChoices.SIDE_A,
240
242
  ),
241
243
  CircuitTerminationPanel(
242
244
  label="Termination - Z Side",
243
245
  section=SectionChoices.RIGHT_HALF,
244
246
  weight=200,
245
247
  context_object_key="circuit_termination_z",
248
+ side=CircuitTerminationSideChoices.SIDE_Z,
246
249
  ),
247
250
  ),
248
251
  )
@@ -1,5 +1,5 @@
1
1
  from nautobot.cloud import filters, models
2
- from nautobot.extras.api.views import NautobotModelViewSet
2
+ from nautobot.extras.api.views import ModelViewSet, NautobotModelViewSet
3
3
 
4
4
  from . import serializers
5
5
 
@@ -26,7 +26,7 @@ class CloudNetworkViewSet(NautobotModelViewSet):
26
26
  filterset_class = filters.CloudNetworkFilterSet
27
27
 
28
28
 
29
- class CloudNetworkPrefixAssignmentViewSet(NautobotModelViewSet):
29
+ class CloudNetworkPrefixAssignmentViewSet(ModelViewSet):
30
30
  queryset = models.CloudNetworkPrefixAssignment.objects.all()
31
31
  serializer_class = serializers.CloudNetworkPrefixAssignmentSerializer
32
32
  filterset_class = filters.CloudNetworkPrefixAssignmentFilterSet
@@ -38,7 +38,7 @@ class CloudServiceViewSet(NautobotModelViewSet):
38
38
  filterset_class = filters.CloudServiceFilterSet
39
39
 
40
40
 
41
- class CloudServiceNetworkAssignmentViewSet(NautobotModelViewSet):
41
+ class CloudServiceNetworkAssignmentViewSet(ModelViewSet):
42
42
  queryset = models.CloudServiceNetworkAssignment.objects.all()
43
43
  serializer_class = serializers.CloudServiceNetworkAssignmentSerializer
44
44
  filterset_class = filters.CloudServiceNetworkAssignmentFilterSet
nautobot/cloud/filters.py CHANGED
@@ -1,5 +1,3 @@
1
- import django_filters
2
-
3
1
  from nautobot.cloud import models
4
2
  from nautobot.core.filters import (
5
3
  BaseFilterSet,
@@ -11,7 +9,7 @@ from nautobot.dcim.models import Manufacturer
11
9
  from nautobot.extras.filters import NautobotFilterSet
12
10
  from nautobot.extras.models import SecretsGroup
13
11
  from nautobot.extras.utils import FeatureQuery
14
- from nautobot.ipam.models import Prefix
12
+ from nautobot.ipam.filters import PrefixFilter
15
13
 
16
14
 
17
15
  class CloudAccountFilterSet(NautobotFilterSet):
@@ -98,7 +96,7 @@ class CloudNetworkFilterSet(NautobotFilterSet):
98
96
  queryset=models.CloudNetwork.objects.all(),
99
97
  label="Parent cloud network (name or ID)",
100
98
  )
101
- prefixes = django_filters.ModelMultipleChoiceFilter(queryset=Prefix.objects.all())
99
+ prefixes = PrefixFilter()
102
100
 
103
101
  class Meta:
104
102
  model = models.CloudNetwork
@@ -117,8 +115,7 @@ class CloudNetworkPrefixAssignmentFilterSet(BaseFilterSet):
117
115
  queryset=models.CloudNetwork.objects.all(),
118
116
  label="Cloud network (name or ID)",
119
117
  )
120
- # Prefix doesn't have an appropriate natural key for NaturalKeyOrPKMultipleChoiceFilter
121
- prefix = django_filters.ModelMultipleChoiceFilter(queryset=Prefix.objects.all())
118
+ prefix = PrefixFilter()
122
119
 
123
120
  class Meta:
124
121
  model = models.CloudNetworkPrefixAssignment
@@ -65,6 +65,7 @@ class CloudNetworkTestCase(FilterTestCases.FilterTestCase):
65
65
  ("name",),
66
66
  ("parent", "parent__id"),
67
67
  ("parent", "parent__name"),
68
+ ("prefixes", "prefixes__id"),
68
69
  ]
69
70
  exclude_q_filter_predicates = [
70
71
  "parent__name",
@@ -79,6 +80,16 @@ class CloudNetworkTestCase(FilterTestCases.FilterTestCase):
79
80
  queryset = queryset.filter(children__isnull=True)
80
81
  return queryset
81
82
 
83
+ def test_prefixes_filter_by_string(self):
84
+ """Test filtering by prefix strings as an alternative to pk."""
85
+ prefix = self.queryset.filter(prefixes__isnull=False).first().prefixes.first()
86
+ params = {"prefixes": [prefix.prefix]}
87
+ self.assertQuerysetEqualAndNotEmpty(
88
+ self.filterset(params, self.queryset).qs,
89
+ self.queryset.filter(prefixes__network=prefix.network, prefixes__prefix_length=prefix.prefix_length),
90
+ ordered=False,
91
+ )
92
+
82
93
 
83
94
  class CloudNetworkPrefixAssignmentTestCase(FilterTestCases.FilterTestCase):
84
95
  queryset = models.CloudNetworkPrefixAssignment.objects.all()
@@ -89,6 +100,16 @@ class CloudNetworkPrefixAssignmentTestCase(FilterTestCases.FilterTestCase):
89
100
  ("prefix", "prefix__id"),
90
101
  ]
91
102
 
103
+ def test_prefix_filter_by_string(self):
104
+ """Test filtering by prefix strings as an alternative to pk."""
105
+ prefix = self.queryset.first().prefix
106
+ params = {"prefix": [prefix.prefix]}
107
+ self.assertQuerysetEqualAndNotEmpty(
108
+ self.filterset(params, self.queryset).qs,
109
+ self.queryset.filter(prefix__network=prefix.network, prefix__prefix_length=prefix.prefix_length),
110
+ ordered=False,
111
+ )
112
+
92
113
 
93
114
  class CloudServiceNetworkAssignmentTestCase(FilterTestCases.FilterTestCase):
94
115
  queryset = models.CloudServiceNetworkAssignment.objects.all()
nautobot/core/admin.py CHANGED
@@ -9,7 +9,9 @@ from django_celery_beat.models import (
9
9
  PeriodicTask,
10
10
  SolarSchedule,
11
11
  )
12
+ import social_django.admin # noqa: F401 # unused-import -- but this import installs the social_django admin
12
13
  from social_django.models import Association, Nonce, UserSocialAuth
14
+ import taggit.admin # noqa: F401 # unused-import -- but this import installs the taggit admin
13
15
  from taggit.models import Tag
14
16
 
15
17
  from nautobot.core.forms import BootstrapMixin
@@ -64,7 +64,6 @@ HTML_ALLOWED_TAGS = nh3.ALLOWED_TAGS - {
64
64
  # at present we just copy nh3.ALLOWED_ATTRIBUTES but we can modify this later as desired and appropriate
65
65
  HTML_ALLOWED_ATTRIBUTES = deepcopy(nh3.ALLOWED_ATTRIBUTES)
66
66
 
67
-
68
67
  #
69
68
  # Reserved Names
70
69
  #
@@ -61,6 +61,7 @@ from nautobot.core.forms.widgets import (
61
61
  APISelect,
62
62
  APISelectMultiple,
63
63
  BulkEditNullBooleanSelect,
64
+ ClearableFileInput,
64
65
  ColorSelect,
65
66
  ContentTypeSelect,
66
67
  DatePicker,
@@ -99,6 +100,7 @@ __all__ = (
99
100
  "CSVModelForm",
100
101
  "CSVMultipleChoiceField",
101
102
  "CSVMultipleContentTypeField",
103
+ "ClearableFileInput",
102
104
  "ColorSelect",
103
105
  "CommentField",
104
106
  "ConfirmationForm",
@@ -10,6 +10,7 @@ from django.forms import formset_factory
10
10
  from django.urls import reverse
11
11
  import yaml
12
12
 
13
+ from nautobot.core.forms import widgets as nautobot_widgets
13
14
  from nautobot.core.utils.filtering import build_lookup_label, get_filter_field_label, get_filterset_parameter_form_field
14
15
  from nautobot.ipam import formfields
15
16
 
@@ -76,9 +77,9 @@ class BootstrapMixin(forms.BaseForm):
76
77
 
77
78
  exempt_widgets = [
78
79
  forms.CheckboxInput,
79
- forms.ClearableFileInput,
80
80
  forms.FileInput,
81
81
  forms.RadioSelect,
82
+ nautobot_widgets.ClearableFileInput,
82
83
  ]
83
84
 
84
85
  for field in self.fields.values():
@@ -13,6 +13,7 @@ __all__ = (
13
13
  "APISelect",
14
14
  "APISelectMultiple",
15
15
  "BulkEditNullBooleanSelect",
16
+ "ClearableFileInput",
16
17
  "ColorSelect",
17
18
  "ContentTypeSelect",
18
19
  "DatePicker",
@@ -256,3 +257,10 @@ class MultiValueCharInput(StaticSelect2Multiple):
256
257
  def __init__(self, *args, **kwargs):
257
258
  super().__init__(*args, **kwargs)
258
259
  self.attrs["class"] = "nautobot-select2-multi-value-char"
260
+
261
+
262
+ class ClearableFileInput(forms.ClearableFileInput):
263
+ template_name = "widgets/clearable_file.html"
264
+
265
+ class Media:
266
+ js = ["bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js"]
@@ -229,7 +229,8 @@ class ExportObjectList(Job):
229
229
  # The force_csv=True attribute is a hack, but much easier than trying to construct a valid HttpRequest
230
230
  # object from scratch that passes all implicit and explicit assumptions in Django and DRF.
231
231
  serializer = serializer_class(queryset, many=True, context={"request": None}, force_csv=True)
232
- csv_data = renderer.render(serializer.data)
232
+ # Explicitly add UTF-8 BOM to the data so that Excel will understand non-ASCII characters correctly...
233
+ csv_data = codecs.BOM_UTF8 + renderer.render(serializer.data).encode("utf-8")
233
234
  self.create_file(filename + ".csv", csv_data)
234
235
 
235
236