views.py 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725
  1. import re
  2. from django.contrib import messages
  3. from django.contrib.auth.decorators import permission_required
  4. from django.contrib.auth.mixins import PermissionRequiredMixin
  5. from django.core.exceptions import ValidationError
  6. from django.core.urlresolvers import reverse
  7. from django.db.models import Count, ProtectedError
  8. from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
  9. from django.http import HttpResponseRedirect
  10. from django.shortcuts import get_object_or_404, redirect, render
  11. from django.utils.http import urlencode
  12. from django.views.generic import View
  13. from ipam.models import Prefix, IPAddress, VLAN
  14. from circuits.models import Circuit
  15. from extras.models import TopologyMap
  16. from utilities.error_handlers import handle_protectederror
  17. from utilities.forms import ConfirmationForm
  18. from utilities.views import (
  19. BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
  20. )
  21. from . import filters, forms, tables
  22. from .models import (
  23. CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
  24. DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
  25. Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
  26. Site,
  27. )
  28. EXPANSION_PATTERN = '\[(\d+-\d+)\]'
  29. def xstr(s):
  30. """
  31. Replace None with an empty string (for CSV export)
  32. """
  33. return '' if s is None else str(s)
  34. def expand_pattern(string):
  35. """
  36. Expand a numeric pattern into a list of strings. Examples:
  37. 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3']
  38. 'xe-0/[0-3]/[0-7]' => ['xe-0/0/0', 'xe-0/0/1', 'xe-0/0/2', ... 'xe-0/3/5', 'xe-0/3/6', 'xe-0/3/7']
  39. """
  40. lead, pattern, remnant = re.split(EXPANSION_PATTERN, string, maxsplit=1)
  41. x, y = pattern.split('-')
  42. for i in range(int(x), int(y) + 1):
  43. if remnant:
  44. for string in expand_pattern(remnant):
  45. yield "{0}{1}{2}".format(lead, i, string)
  46. else:
  47. yield "{0}{1}".format(lead, i)
  48. #
  49. # Sites
  50. #
  51. class SiteListView(ObjectListView):
  52. queryset = Site.objects.all()
  53. filter = filters.SiteFilter
  54. table = tables.SiteTable
  55. template_name = 'dcim/site_list.html'
  56. def site(request, slug):
  57. site = get_object_or_404(Site, slug=slug)
  58. stats = {
  59. 'rack_count': Rack.objects.filter(site=site).count(),
  60. 'device_count': Device.objects.filter(rack__site=site).count(),
  61. 'prefix_count': Prefix.objects.filter(site=site).count(),
  62. 'vlan_count': VLAN.objects.filter(site=site).count(),
  63. 'circuit_count': Circuit.objects.filter(site=site).count(),
  64. }
  65. rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
  66. topology_maps = TopologyMap.objects.filter(site=site)
  67. return render(request, 'dcim/site.html', {
  68. 'site': site,
  69. 'stats': stats,
  70. 'rack_groups': rack_groups,
  71. 'topology_maps': topology_maps,
  72. })
  73. class SiteEditView(PermissionRequiredMixin, ObjectEditView):
  74. permission_required = 'dcim.change_site'
  75. model = Site
  76. form_class = forms.SiteForm
  77. template_name = 'dcim/site_edit.html'
  78. cancel_url = 'dcim:site_list'
  79. class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  80. permission_required = 'dcim.delete_site'
  81. model = Site
  82. redirect_url = 'dcim:site_list'
  83. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  84. permission_required = 'dcim.add_site'
  85. form = forms.SiteImportForm
  86. table = tables.SiteTable
  87. template_name = 'dcim/site_import.html'
  88. obj_list_url = 'dcim:site_list'
  89. #
  90. # Rack groups
  91. #
  92. class RackGroupListView(ObjectListView):
  93. queryset = RackGroup.objects.annotate(rack_count=Count('racks'))
  94. filter = filters.RackGroupFilter
  95. filter_form = forms.RackGroupFilterForm
  96. table = tables.RackGroupTable
  97. edit_permissions = ['dcim.change_rackgroup', 'dcim.delete_rackgroup']
  98. template_name = 'dcim/rackgroup_list.html'
  99. class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
  100. permission_required = 'dcim.change_rackgroup'
  101. model = RackGroup
  102. form_class = forms.RackGroupForm
  103. cancel_url = 'dcim:rackgroup_list'
  104. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  105. permission_required = 'dcim.delete_rackgroup'
  106. cls = RackGroup
  107. form = forms.RackGroupBulkDeleteForm
  108. default_redirect_url = 'dcim:rackgroup_list'
  109. #
  110. # Racks
  111. #
  112. class RackListView(ObjectListView):
  113. queryset = Rack.objects.select_related('site').annotate(device_count=Count('devices', distinct=True))
  114. filter = filters.RackFilter
  115. filter_form = forms.RackFilterForm
  116. table = tables.RackTable
  117. edit_permissions = ['dcim.change_rack', 'dcim.delete_rack']
  118. template_name = 'dcim/rack_list.html'
  119. def rack(request, pk):
  120. rack = get_object_or_404(Rack, pk=pk)
  121. nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True)\
  122. .select_related('device_type__manufacturer')
  123. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  124. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  125. return render(request, 'dcim/rack.html', {
  126. 'rack': rack,
  127. 'nonracked_devices': nonracked_devices,
  128. 'next_rack': next_rack,
  129. 'prev_rack': prev_rack,
  130. 'front_elevation': rack.get_front_elevation(),
  131. 'rear_elevation': rack.get_rear_elevation(),
  132. })
  133. class RackEditView(PermissionRequiredMixin, ObjectEditView):
  134. permission_required = 'dcim.change_rack'
  135. model = Rack
  136. form_class = forms.RackForm
  137. template_name = 'dcim/rack_edit.html'
  138. cancel_url = 'dcim:rack_list'
  139. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  140. permission_required = 'dcim.delete_rack'
  141. model = Rack
  142. redirect_url = 'dcim:rack_list'
  143. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  144. permission_required = 'dcim.add_rack'
  145. form = forms.RackImportForm
  146. table = tables.RackTable
  147. template_name = 'dcim/rack_import.html'
  148. obj_list_url = 'dcim:rack_list'
  149. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  150. permission_required = 'dcim.change_rack'
  151. cls = Rack
  152. form = forms.RackBulkEditForm
  153. template_name = 'dcim/rack_bulk_edit.html'
  154. default_redirect_url = 'dcim:rack_list'
  155. def update_objects(self, pk_list, form):
  156. fields_to_update = {}
  157. for field in ['site', 'group', 'u_height', 'comments']:
  158. if form.cleaned_data[field]:
  159. fields_to_update[field] = form.cleaned_data[field]
  160. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  161. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  162. permission_required = 'dcim.delete_rack'
  163. cls = Rack
  164. form = forms.RackBulkDeleteForm
  165. default_redirect_url = 'dcim:rack_list'
  166. #
  167. # Manufacturers
  168. #
  169. class ManufacturerListView(ObjectListView):
  170. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  171. table = tables.ManufacturerTable
  172. edit_permissions = ['dcim.change_manufacturer', 'dcim.delete_manufacturer']
  173. template_name = 'dcim/manufacturer_list.html'
  174. class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
  175. permission_required = 'dcim.change_manufacturer'
  176. model = Manufacturer
  177. form_class = forms.ManufacturerForm
  178. success_url = 'dcim:manufacturer_list'
  179. cancel_url = 'dcim:manufacturer_list'
  180. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  181. permission_required = 'dcim.delete_manufacturer'
  182. cls = Manufacturer
  183. form = forms.ManufacturerBulkDeleteForm
  184. default_redirect_url = 'dcim:manufacturer_list'
  185. #
  186. # Device types
  187. #
  188. class DeviceTypeListView(ObjectListView):
  189. queryset = DeviceType.objects.select_related('manufacturer')
  190. filter = filters.DeviceTypeFilter
  191. filter_form = forms.DeviceTypeFilterForm
  192. table = tables.DeviceTypeTable
  193. edit_permissions = ['dcim.change_devicetype', 'dcim.delete_devicetype']
  194. template_name = 'dcim/devicetype_list.html'
  195. def devicetype(request, pk):
  196. devicetype = get_object_or_404(DeviceType, pk=pk)
  197. # Component tables
  198. consoleport_table = tables.ConsolePortTemplateTable(ConsolePortTemplate.objects.filter(device_type=devicetype))
  199. consoleserverport_table = tables.ConsoleServerPortTemplateTable(ConsoleServerPortTemplate.objects
  200. .filter(device_type=devicetype))
  201. powerport_table = tables.PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
  202. poweroutlet_table = tables.PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
  203. interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
  204. devicebay_table = tables.DeviceBayTemplateTable(DeviceBayTemplate.objects.filter(device_type=devicetype))
  205. if request.user.has_perm('dcim.change_devicetype'):
  206. consoleport_table.base_columns['pk'].visible = True
  207. consoleserverport_table.base_columns['pk'].visible = True
  208. powerport_table.base_columns['pk'].visible = True
  209. poweroutlet_table.base_columns['pk'].visible = True
  210. interface_table.base_columns['pk'].visible = True
  211. devicebay_table.base_columns['pk'].visible = True
  212. return render(request, 'dcim/devicetype.html', {
  213. 'devicetype': devicetype,
  214. 'consoleport_table': consoleport_table,
  215. 'consoleserverport_table': consoleserverport_table,
  216. 'powerport_table': powerport_table,
  217. 'poweroutlet_table': poweroutlet_table,
  218. 'interface_table': interface_table,
  219. 'devicebay_table': devicebay_table,
  220. })
  221. class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
  222. permission_required = 'dcim.change_devicetype'
  223. model = DeviceType
  224. form_class = forms.DeviceTypeForm
  225. cancel_url = 'dcim:devicetype_list'
  226. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  227. permission_required = 'dcim.delete_devicetype'
  228. model = DeviceType
  229. redirect_url = 'dcim:devicetype_list'
  230. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  231. permission_required = 'dcim.change_devicetype'
  232. cls = DeviceType
  233. form = forms.DeviceTypeBulkEditForm
  234. template_name = 'dcim/devicetype_bulk_edit.html'
  235. default_redirect_url = 'dcim:devicetype_list'
  236. def update_objects(self, pk_list, form):
  237. fields_to_update = {}
  238. for field in ['manufacturer', 'u_height']:
  239. if form.cleaned_data[field]:
  240. fields_to_update[field] = form.cleaned_data[field]
  241. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  242. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  243. permission_required = 'dcim.delete_devicetype'
  244. cls = DeviceType
  245. form = forms.DeviceTypeBulkDeleteForm
  246. default_redirect_url = 'dcim:devicetype_list'
  247. #
  248. # Device type components
  249. #
  250. class ComponentTemplateCreateView(View):
  251. model = None
  252. form = None
  253. def get(self, request, pk):
  254. devicetype = get_object_or_404(DeviceType, pk=pk)
  255. return render(request, 'dcim/component_template_add.html', {
  256. 'devicetype': devicetype,
  257. 'component_type': self.model._meta.verbose_name,
  258. 'form': self.form(),
  259. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  260. })
  261. def post(self, request, pk):
  262. devicetype = get_object_or_404(DeviceType, pk=pk)
  263. form = self.form(request.POST)
  264. if form.is_valid():
  265. component_templates = []
  266. for name in form.cleaned_data['name_pattern']:
  267. component_template = self.form(request.POST).save(commit=False)
  268. component_template.device_type = devicetype
  269. component_template.name = name
  270. try:
  271. component_template.full_clean()
  272. component_templates.append(component_template)
  273. except ValidationError:
  274. form.add_error('name_pattern', "Duplicate name found: {}".format(name))
  275. if not form.errors:
  276. self.model.objects.bulk_create(component_templates)
  277. messages.success(request, "Added {} component(s) to {}".format(len(component_templates), devicetype))
  278. if '_addanother' in request.POST:
  279. return redirect(request.path)
  280. else:
  281. return redirect('dcim:devicetype', pk=devicetype.pk)
  282. return render(request, 'dcim/component_template_add.html', {
  283. 'devicetype': devicetype,
  284. 'component_type': self.model._meta.verbose_name,
  285. 'form': form,
  286. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  287. })
  288. class ConsolePortTemplateAddView(ComponentTemplateCreateView):
  289. model = ConsolePortTemplate
  290. form = forms.ConsolePortTemplateForm
  291. class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
  292. model = ConsoleServerPortTemplate
  293. form = forms.ConsoleServerPortTemplateForm
  294. class PowerPortTemplateAddView(ComponentTemplateCreateView):
  295. model = PowerPortTemplate
  296. form = forms.PowerPortTemplateForm
  297. class PowerOutletTemplateAddView(ComponentTemplateCreateView):
  298. model = PowerOutletTemplate
  299. form = forms.PowerOutletTemplateForm
  300. class InterfaceTemplateAddView(ComponentTemplateCreateView):
  301. model = InterfaceTemplate
  302. form = forms.InterfaceTemplateForm
  303. class DeviceBayTemplateAddView(ComponentTemplateCreateView):
  304. model = DeviceBayTemplate
  305. form = forms.DeviceBayTemplateForm
  306. def component_template_delete(request, pk, model):
  307. devicetype = get_object_or_404(DeviceType, pk=pk)
  308. class ComponentTemplateBulkDeleteForm(ConfirmationForm):
  309. pk = ModelMultipleChoiceField(queryset=model.objects.all(), widget=MultipleHiddenInput)
  310. if '_confirm' in request.POST:
  311. form = ComponentTemplateBulkDeleteForm(request.POST)
  312. if form.is_valid():
  313. # Delete component templates
  314. objects_to_delete = model.objects.filter(pk__in=[v.id for v in form.cleaned_data['pk']])
  315. try:
  316. deleted_count = objects_to_delete.count()
  317. objects_to_delete.delete()
  318. except ProtectedError, e:
  319. handle_protectederror(list(objects_to_delete), request, e)
  320. return redirect('dcim:devicetype', {'pk': devicetype.pk})
  321. messages.success(request, "Deleted {} {}".format(deleted_count, model._meta.verbose_name_plural))
  322. return redirect('dcim:devicetype', pk=devicetype.pk)
  323. else:
  324. form = ComponentTemplateBulkDeleteForm(initial={'pk': request.POST.getlist('pk')})
  325. selected_objects = model.objects.filter(pk__in=form.initial.get('pk'))
  326. if not selected_objects:
  327. messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
  328. return redirect('dcim:devicetype', pk=devicetype.pk)
  329. return render(request, 'dcim/component_template_delete.html', {
  330. 'devicetype': devicetype,
  331. 'form': form,
  332. 'selected_objects': selected_objects,
  333. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  334. })
  335. #
  336. # Device roles
  337. #
  338. class DeviceRoleListView(ObjectListView):
  339. queryset = DeviceRole.objects.annotate(device_count=Count('devices'))
  340. table = tables.DeviceRoleTable
  341. edit_permissions = ['dcim.change_devicerole', 'dcim.delete_devicerole']
  342. template_name = 'dcim/devicerole_list.html'
  343. class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
  344. permission_required = 'dcim.change_devicerole'
  345. model = DeviceRole
  346. form_class = forms.DeviceRoleForm
  347. success_url = 'dcim:devicerole_list'
  348. cancel_url = 'dcim:devicerole_list'
  349. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  350. permission_required = 'dcim.delete_devicerole'
  351. cls = DeviceRole
  352. form = forms.DeviceRoleBulkDeleteForm
  353. default_redirect_url = 'dcim:devicerole_list'
  354. #
  355. # Platforms
  356. #
  357. class PlatformListView(ObjectListView):
  358. queryset = Platform.objects.annotate(device_count=Count('devices'))
  359. table = tables.PlatformTable
  360. edit_permissions = ['dcim.change_platform', 'dcim.delete_platform']
  361. template_name = 'dcim/platform_list.html'
  362. class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
  363. permission_required = 'dcim.change_platform'
  364. model = Platform
  365. form_class = forms.PlatformForm
  366. success_url = 'dcim:platform_list'
  367. cancel_url = 'dcim:platform_list'
  368. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  369. permission_required = 'dcim.delete_platform'
  370. cls = Platform
  371. form = forms.PlatformBulkDeleteForm
  372. default_redirect_url = 'dcim:platform_list'
  373. #
  374. # Devices
  375. #
  376. class DeviceListView(ObjectListView):
  377. queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip')
  378. filter = filters.DeviceFilter
  379. filter_form = forms.DeviceFilterForm
  380. table = tables.DeviceTable
  381. edit_permissions = ['dcim.change_device', 'dcim.delete_device']
  382. template_name = 'dcim/device_list.html'
  383. def device(request, pk):
  384. device = get_object_or_404(Device, pk=pk)
  385. console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
  386. cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
  387. power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
  388. power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
  389. interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
  390. .select_related('connected_as_a', 'connected_as_b', 'circuit')
  391. mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
  392. .select_related('connected_as_a', 'connected_as_b', 'circuit')
  393. device_bays = DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer')
  394. # Gather any secrets which belong to this device
  395. secrets = device.secrets.all()
  396. # Find all IP addresses assigned to this device
  397. ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface').order_by('address')
  398. # Find any related devices for convenient linking in the UI
  399. related_devices = []
  400. if device.name:
  401. if re.match('.+[0-9]+$', device.name):
  402. # Strip 1 or more trailing digits (e.g. core-switch1)
  403. base_name = re.match('(.*?)[0-9]+$', device.name).group(1)
  404. elif re.match('.+\d[a-z]$', device.name.lower()):
  405. # Strip a trailing letter if preceded by a digit (e.g. dist-switch3a -> dist-switch3)
  406. base_name = re.match('(.*\d+)[a-z]$', device.name.lower()).group(1)
  407. else:
  408. base_name = None
  409. if base_name:
  410. related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk)\
  411. .select_related('rack', 'device_type__manufacturer')[:10]
  412. return render(request, 'dcim/device.html', {
  413. 'device': device,
  414. 'console_ports': console_ports,
  415. 'cs_ports': cs_ports,
  416. 'power_ports': power_ports,
  417. 'power_outlets': power_outlets,
  418. 'interfaces': interfaces,
  419. 'mgmt_interfaces': mgmt_interfaces,
  420. 'device_bays': device_bays,
  421. 'ip_addresses': ip_addresses,
  422. 'secrets': secrets,
  423. 'related_devices': related_devices,
  424. })
  425. class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
  426. permission_required = 'dcim.change_device'
  427. model = Device
  428. form_class = forms.DeviceForm
  429. fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
  430. template_name = 'dcim/device_edit.html'
  431. cancel_url = 'dcim:device_list'
  432. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  433. permission_required = 'dcim.delete_device'
  434. model = Device
  435. redirect_url = 'dcim:device_list'
  436. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  437. permission_required = 'dcim.add_device'
  438. form = forms.DeviceImportForm
  439. table = tables.DeviceImportTable
  440. template_name = 'dcim/device_import.html'
  441. obj_list_url = 'dcim:device_list'
  442. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  443. permission_required = 'dcim.change_device'
  444. cls = Device
  445. form = forms.DeviceBulkEditForm
  446. template_name = 'dcim/device_bulk_edit.html'
  447. default_redirect_url = 'dcim:device_list'
  448. def update_objects(self, pk_list, form):
  449. fields_to_update = {}
  450. if form.cleaned_data['platform']:
  451. fields_to_update['platform'] = form.cleaned_data['platform']
  452. elif form.cleaned_data['platform_delete']:
  453. fields_to_update['platform'] = None
  454. if form.cleaned_data['status']:
  455. status = form.cleaned_data['status']
  456. fields_to_update['status'] = True if status == 'True' else False
  457. for field in ['device_type', 'device_role', 'serial']:
  458. if form.cleaned_data[field]:
  459. fields_to_update[field] = form.cleaned_data[field]
  460. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  461. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  462. permission_required = 'dcim.delete_device'
  463. cls = Device
  464. form = forms.DeviceBulkDeleteForm
  465. default_redirect_url = 'dcim:device_list'
  466. def device_inventory(request, pk):
  467. device = get_object_or_404(Device, pk=pk)
  468. modules = Module.objects.filter(device=device, parent=None).prefetch_related('submodules')
  469. return render(request, 'dcim/device_inventory.html', {
  470. 'device': device,
  471. 'modules': modules,
  472. })
  473. def device_lldp_neighbors(request, pk):
  474. device = get_object_or_404(Device, pk=pk)
  475. interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b')
  476. return render(request, 'dcim/device_lldp_neighbors.html', {
  477. 'device': device,
  478. 'interfaces': interfaces,
  479. })
  480. #
  481. # Console ports
  482. #
  483. @permission_required('dcim.add_consoleport')
  484. def consoleport_add(request, pk):
  485. device = get_object_or_404(Device, pk=pk)
  486. if request.method == 'POST':
  487. form = forms.ConsolePortCreateForm(request.POST)
  488. if form.is_valid():
  489. console_ports = []
  490. for name in form.cleaned_data['name_pattern']:
  491. cp_form = forms.ConsolePortForm({
  492. 'device': device.pk,
  493. 'name': name,
  494. })
  495. if cp_form.is_valid():
  496. console_ports.append(cp_form.save(commit=False))
  497. else:
  498. form.add_error('name_pattern', "Duplicate console port name for this device: {}".format(name))
  499. if not form.errors:
  500. ConsolePort.objects.bulk_create(console_ports)
  501. messages.success(request, "Added {} console port(s) to {}".format(len(console_ports), device))
  502. if '_addanother' in request.POST:
  503. return redirect('dcim:consoleport_add', pk=device.pk)
  504. else:
  505. return redirect('dcim:device', pk=device.pk)
  506. else:
  507. form = forms.ConsolePortCreateForm()
  508. return render(request, 'dcim/consoleport_edit.html', {
  509. 'device': device,
  510. 'form': form,
  511. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  512. })
  513. @permission_required('dcim.change_consoleport')
  514. def consoleport_connect(request, pk):
  515. consoleport = get_object_or_404(ConsolePort, pk=pk)
  516. if request.method == 'POST':
  517. form = forms.ConsolePortConnectionForm(request.POST, instance=consoleport)
  518. if form.is_valid():
  519. consoleport = form.save()
  520. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  521. consoleport.device,
  522. consoleport.name,
  523. consoleport.cs_port.device,
  524. consoleport.cs_port.name,
  525. ))
  526. return redirect('dcim:device', pk=consoleport.device.pk)
  527. else:
  528. form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
  529. 'rack': consoleport.device.rack,
  530. 'connection_status': CONNECTION_STATUS_CONNECTED,
  531. })
  532. return render(request, 'dcim/consoleport_connect.html', {
  533. 'consoleport': consoleport,
  534. 'form': form,
  535. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  536. })
  537. @permission_required('dcim.change_consoleport')
  538. def consoleport_disconnect(request, pk):
  539. consoleport = get_object_or_404(ConsolePort, pk=pk)
  540. if not consoleport.cs_port:
  541. messages.warning(request, "Cannot disconnect console port {0}: It is not connected to anything"
  542. .format(consoleport))
  543. return redirect('dcim:device', pk=consoleport.device.pk)
  544. if request.method == 'POST':
  545. form = ConfirmationForm(request.POST)
  546. if form.is_valid():
  547. consoleport.cs_port = None
  548. consoleport.connection_status = None
  549. consoleport.save()
  550. messages.success(request, "Console port {0} has been disconnected".format(consoleport))
  551. return redirect('dcim:device', pk=consoleport.device.pk)
  552. else:
  553. form = ConfirmationForm()
  554. return render(request, 'dcim/consoleport_disconnect.html', {
  555. 'consoleport': consoleport,
  556. 'form': form,
  557. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  558. })
  559. @permission_required('dcim.change_consoleport')
  560. def consoleport_edit(request, pk):
  561. consoleport = get_object_or_404(ConsolePort, pk=pk)
  562. if request.method == 'POST':
  563. form = forms.ConsolePortForm(request.POST, instance=consoleport)
  564. if form.is_valid():
  565. consoleport = form.save()
  566. messages.success(request, "Modified {0} {1}".format(consoleport.device.name, consoleport.name))
  567. return redirect('dcim:device', pk=consoleport.device.pk)
  568. else:
  569. form = forms.ConsolePortForm(instance=consoleport)
  570. return render(request, 'dcim/consoleport_edit.html', {
  571. 'consoleport': consoleport,
  572. 'form': form,
  573. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  574. })
  575. @permission_required('dcim.delete_consoleport')
  576. def consoleport_delete(request, pk):
  577. consoleport = get_object_or_404(ConsolePort, pk=pk)
  578. if request.method == 'POST':
  579. form = ConfirmationForm(request.POST)
  580. if form.is_valid():
  581. consoleport.delete()
  582. messages.success(request, "Console port {0} has been deleted from {1}".format(consoleport,
  583. consoleport.device))
  584. return redirect('dcim:device', pk=consoleport.device.pk)
  585. else:
  586. form = ConfirmationForm()
  587. return render(request, 'dcim/consoleport_delete.html', {
  588. 'consoleport': consoleport,
  589. 'form': form,
  590. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  591. })
  592. class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  593. permission_required = 'dcim.change_consoleport'
  594. form = forms.ConsoleConnectionImportForm
  595. table = tables.ConsoleConnectionTable
  596. template_name = 'dcim/console_connections_import.html'
  597. #
  598. # Console server ports
  599. #
  600. @permission_required('dcim.add_consoleserverport')
  601. def consoleserverport_add(request, pk):
  602. device = get_object_or_404(Device, pk=pk)
  603. if request.method == 'POST':
  604. form = forms.ConsoleServerPortCreateForm(request.POST)
  605. if form.is_valid():
  606. cs_ports = []
  607. for name in form.cleaned_data['name_pattern']:
  608. csp_form = forms.ConsoleServerPortForm({
  609. 'device': device.pk,
  610. 'name': name,
  611. })
  612. if csp_form.is_valid():
  613. cs_ports.append(csp_form.save(commit=False))
  614. else:
  615. form.add_error('name_pattern', "Duplicate console server port name for this device: {}"
  616. .format(name))
  617. if not form.errors:
  618. ConsoleServerPort.objects.bulk_create(cs_ports)
  619. messages.success(request, "Added {} console server port(s) to {}".format(len(cs_ports), device))
  620. if '_addanother' in request.POST:
  621. return redirect('dcim:consoleserverport_add', pk=device.pk)
  622. else:
  623. return redirect('dcim:device', pk=device.pk)
  624. else:
  625. form = forms.ConsoleServerPortCreateForm()
  626. return render(request, 'dcim/consoleserverport_edit.html', {
  627. 'device': device,
  628. 'form': form,
  629. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  630. })
  631. @permission_required('dcim.change_consoleserverport')
  632. def consoleserverport_connect(request, pk):
  633. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  634. if request.method == 'POST':
  635. form = forms.ConsoleServerPortConnectionForm(consoleserverport, request.POST)
  636. if form.is_valid():
  637. consoleport = form.cleaned_data['port']
  638. consoleport.cs_port = consoleserverport
  639. consoleport.connection_status = form.cleaned_data['connection_status']
  640. consoleport.save()
  641. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  642. consoleport.device,
  643. consoleport.name,
  644. consoleserverport.device,
  645. consoleserverport.name,
  646. ))
  647. return redirect('dcim:device', pk=consoleserverport.device.pk)
  648. else:
  649. form = forms.ConsoleServerPortConnectionForm(consoleserverport, initial={'rack': consoleserverport.device.rack})
  650. return render(request, 'dcim/consoleserverport_connect.html', {
  651. 'consoleserverport': consoleserverport,
  652. 'form': form,
  653. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  654. })
  655. @permission_required('dcim.change_consoleserverport')
  656. def consoleserverport_disconnect(request, pk):
  657. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  658. if not hasattr(consoleserverport, 'connected_console'):
  659. messages.warning(request, "Cannot disconnect console server port {0}: Nothing is connected to it"
  660. .format(consoleserverport))
  661. return redirect('dcim:device', pk=consoleserverport.device.pk)
  662. if request.method == 'POST':
  663. form = ConfirmationForm(request.POST)
  664. if form.is_valid():
  665. consoleport = consoleserverport.connected_console
  666. consoleport.cs_port = None
  667. consoleport.connection_status = None
  668. consoleport.save()
  669. messages.success(request, "Console server port {0} has been disconnected".format(consoleserverport))
  670. return redirect('dcim:device', pk=consoleserverport.device.pk)
  671. else:
  672. form = ConfirmationForm()
  673. return render(request, 'dcim/consoleserverport_disconnect.html', {
  674. 'consoleserverport': consoleserverport,
  675. 'form': form,
  676. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  677. })
  678. @permission_required('dcim.change_consoleserverport')
  679. def consoleserverport_edit(request, pk):
  680. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  681. if request.method == 'POST':
  682. form = forms.ConsoleServerPortForm(request.POST, instance=consoleserverport)
  683. if form.is_valid():
  684. consoleserverport = form.save()
  685. messages.success(request, "Modified {0} {1}".format(consoleserverport.device.name, consoleserverport.name))
  686. return redirect('dcim:device', pk=consoleserverport.device.pk)
  687. else:
  688. form = forms.ConsoleServerPortForm(instance=consoleserverport)
  689. return render(request, 'dcim/consoleserverport_edit.html', {
  690. 'consoleserverport': consoleserverport,
  691. 'form': form,
  692. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  693. })
  694. @permission_required('dcim.delete_consoleserverport')
  695. def consoleserverport_delete(request, pk):
  696. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  697. if request.method == 'POST':
  698. form = ConfirmationForm(request.POST)
  699. if form.is_valid():
  700. consoleserverport.delete()
  701. messages.success(request, "Console server port {0} has been deleted from {1}"
  702. .format(consoleserverport, consoleserverport.device))
  703. return redirect('dcim:device', pk=consoleserverport.device.pk)
  704. else:
  705. form = ConfirmationForm()
  706. return render(request, 'dcim/consoleserverport_delete.html', {
  707. 'consoleserverport': consoleserverport,
  708. 'form': form,
  709. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  710. })
  711. #
  712. # Power ports
  713. #
  714. @permission_required('dcim.add_powerport')
  715. def powerport_add(request, pk):
  716. device = get_object_or_404(Device, pk=pk)
  717. if request.method == 'POST':
  718. form = forms.PowerPortCreateForm(request.POST)
  719. if form.is_valid():
  720. power_ports = []
  721. for name in form.cleaned_data['name_pattern']:
  722. pp_form = forms.PowerPortForm({
  723. 'device': device.pk,
  724. 'name': name,
  725. })
  726. if pp_form.is_valid():
  727. power_ports.append(pp_form.save(commit=False))
  728. else:
  729. form.add_error('name_pattern', "Duplicate power port name for this device: {}".format(name))
  730. if not form.errors:
  731. PowerPort.objects.bulk_create(power_ports)
  732. messages.success(request, "Added {} power port(s) to {}".format(len(power_ports), device))
  733. if '_addanother' in request.POST:
  734. return redirect('dcim:powerport_add', pk=device.pk)
  735. else:
  736. return redirect('dcim:device', pk=device.pk)
  737. else:
  738. form = forms.PowerPortCreateForm()
  739. return render(request, 'dcim/powerport_edit.html', {
  740. 'device': device,
  741. 'form': form,
  742. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  743. })
  744. @permission_required('dcim.change_powerport')
  745. def powerport_connect(request, pk):
  746. powerport = get_object_or_404(PowerPort, pk=pk)
  747. if request.method == 'POST':
  748. form = forms.PowerPortConnectionForm(request.POST, instance=powerport)
  749. if form.is_valid():
  750. powerport = form.save()
  751. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  752. powerport.device,
  753. powerport.name,
  754. powerport.power_outlet.device,
  755. powerport.power_outlet.name,
  756. ))
  757. return redirect('dcim:device', pk=powerport.device.pk)
  758. else:
  759. form = forms.PowerPortConnectionForm(instance=powerport, initial={
  760. 'rack': powerport.device.rack,
  761. 'connection_status': CONNECTION_STATUS_CONNECTED,
  762. })
  763. return render(request, 'dcim/powerport_connect.html', {
  764. 'powerport': powerport,
  765. 'form': form,
  766. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  767. })
  768. @permission_required('dcim.change_powerport')
  769. def powerport_disconnect(request, pk):
  770. powerport = get_object_or_404(PowerPort, pk=pk)
  771. if not powerport.power_outlet:
  772. messages.warning(request, "Cannot disconnect power port {0}: It is not connected to an outlet"
  773. .format(powerport))
  774. return redirect('dcim:device', pk=powerport.device.pk)
  775. if request.method == 'POST':
  776. form = ConfirmationForm(request.POST)
  777. if form.is_valid():
  778. powerport.power_outlet = None
  779. powerport.connection_status = None
  780. powerport.save()
  781. messages.success(request, "Power port {0} has been disconnected".format(powerport))
  782. return redirect('dcim:device', pk=powerport.device.pk)
  783. else:
  784. form = ConfirmationForm()
  785. return render(request, 'dcim/powerport_disconnect.html', {
  786. 'powerport': powerport,
  787. 'form': form,
  788. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  789. })
  790. @permission_required('dcim.change_powerport')
  791. def powerport_edit(request, pk):
  792. powerport = get_object_or_404(PowerPort, pk=pk)
  793. if request.method == 'POST':
  794. form = forms.PowerPortForm(request.POST, instance=powerport)
  795. if form.is_valid():
  796. powerport = form.save()
  797. messages.success(request, "Modified {0} power port {1}".format(powerport.device.name, powerport.name))
  798. return redirect('dcim:device', pk=powerport.device.pk)
  799. else:
  800. form = forms.PowerPortForm(instance=powerport)
  801. return render(request, 'dcim/powerport_edit.html', {
  802. 'powerport': powerport,
  803. 'form': form,
  804. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  805. })
  806. @permission_required('dcim.delete_powerport')
  807. def powerport_delete(request, pk):
  808. powerport = get_object_or_404(PowerPort, pk=pk)
  809. if request.method == 'POST':
  810. form = ConfirmationForm(request.POST)
  811. if form.is_valid():
  812. powerport.delete()
  813. messages.success(request, "Power port {0} has been deleted from {1}".format(powerport, powerport.device))
  814. return redirect('dcim:device', pk=powerport.device.pk)
  815. else:
  816. form = ConfirmationForm()
  817. return render(request, 'dcim/powerport_delete.html', {
  818. 'powerport': powerport,
  819. 'form': form,
  820. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  821. })
  822. class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  823. permission_required = 'dcim.change_powerport'
  824. form = forms.PowerConnectionImportForm
  825. table = tables.PowerConnectionTable
  826. template_name = 'dcim/power_connections_import.html'
  827. #
  828. # Power outlets
  829. #
  830. @permission_required('dcim.add_poweroutlet')
  831. def poweroutlet_add(request, pk):
  832. device = get_object_or_404(Device, pk=pk)
  833. if request.method == 'POST':
  834. form = forms.PowerOutletCreateForm(request.POST)
  835. if form.is_valid():
  836. power_outlets = []
  837. for name in form.cleaned_data['name_pattern']:
  838. po_form = forms.PowerOutletForm({
  839. 'device': device.pk,
  840. 'name': name,
  841. })
  842. if po_form.is_valid():
  843. power_outlets.append(po_form.save(commit=False))
  844. else:
  845. form.add_error('name_pattern', "Duplicate power outlet name for this device: {}".format(name))
  846. if not form.errors:
  847. PowerOutlet.objects.bulk_create(power_outlets)
  848. messages.success(request, "Added {} power outlet(s) to {}".format(len(power_outlets), device))
  849. if '_addanother' in request.POST:
  850. return redirect('dcim:poweroutlet_add', pk=device.pk)
  851. else:
  852. return redirect('dcim:device', pk=device.pk)
  853. else:
  854. form = forms.PowerOutletCreateForm()
  855. return render(request, 'dcim/poweroutlet_edit.html', {
  856. 'device': device,
  857. 'form': form,
  858. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  859. })
  860. @permission_required('dcim.change_poweroutlet')
  861. def poweroutlet_connect(request, pk):
  862. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  863. if request.method == 'POST':
  864. form = forms.PowerOutletConnectionForm(poweroutlet, request.POST)
  865. if form.is_valid():
  866. powerport = form.cleaned_data['port']
  867. powerport.power_outlet = poweroutlet
  868. powerport.connection_status = form.cleaned_data['connection_status']
  869. powerport.save()
  870. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  871. powerport.device,
  872. powerport.name,
  873. poweroutlet.device,
  874. poweroutlet.name,
  875. ))
  876. return redirect('dcim:device', pk=poweroutlet.device.pk)
  877. else:
  878. form = forms.PowerOutletConnectionForm(poweroutlet, initial={'rack': poweroutlet.device.rack})
  879. return render(request, 'dcim/poweroutlet_connect.html', {
  880. 'poweroutlet': poweroutlet,
  881. 'form': form,
  882. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  883. })
  884. @permission_required('dcim.change_poweroutlet')
  885. def poweroutlet_disconnect(request, pk):
  886. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  887. if not hasattr(poweroutlet, 'connected_port'):
  888. messages.warning(request, "Cannot disconnect power outlet {0}: Nothing is connected to it".format(poweroutlet))
  889. return redirect('dcim:device', pk=poweroutlet.device.pk)
  890. if request.method == 'POST':
  891. form = ConfirmationForm(request.POST)
  892. if form.is_valid():
  893. powerport = poweroutlet.connected_port
  894. powerport.power_outlet = None
  895. powerport.connection_status = None
  896. powerport.save()
  897. messages.success(request, "Power outlet {0} has been disconnected".format(poweroutlet))
  898. return redirect('dcim:device', pk=poweroutlet.device.pk)
  899. else:
  900. form = ConfirmationForm()
  901. return render(request, 'dcim/poweroutlet_disconnect.html', {
  902. 'poweroutlet': poweroutlet,
  903. 'form': form,
  904. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  905. })
  906. @permission_required('dcim.change_poweroutlet')
  907. def poweroutlet_edit(request, pk):
  908. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  909. if request.method == 'POST':
  910. form = forms.PowerOutletForm(request.POST, instance=poweroutlet)
  911. if form.is_valid():
  912. poweroutlet = form.save()
  913. messages.success(request, "Modified {0} power outlet {1}".format(poweroutlet.device.name, poweroutlet.name))
  914. return redirect('dcim:device', pk=poweroutlet.device.pk)
  915. else:
  916. form = forms.PowerOutletForm(instance=poweroutlet)
  917. return render(request, 'dcim/poweroutlet_edit.html', {
  918. 'poweroutlet': poweroutlet,
  919. 'form': form,
  920. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  921. })
  922. @permission_required('dcim.delete_poweroutlet')
  923. def poweroutlet_delete(request, pk):
  924. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  925. if request.method == 'POST':
  926. form = ConfirmationForm(request.POST)
  927. if form.is_valid():
  928. poweroutlet.delete()
  929. messages.success(request, "Power outlet {0} has been deleted from {1}".format(poweroutlet,
  930. poweroutlet.device))
  931. return redirect('dcim:device', pk=poweroutlet.device.pk)
  932. else:
  933. form = ConfirmationForm()
  934. return render(request, 'dcim/poweroutlet_delete.html', {
  935. 'poweroutlet': poweroutlet,
  936. 'form': form,
  937. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  938. })
  939. #
  940. # Interfaces
  941. #
  942. @permission_required('dcim.add_interface')
  943. def interface_add(request, pk):
  944. device = get_object_or_404(Device, pk=pk)
  945. if request.method == 'POST':
  946. form = forms.InterfaceCreateForm(request.POST)
  947. if form.is_valid():
  948. interfaces = []
  949. for name in form.cleaned_data['name_pattern']:
  950. iface_form = forms.InterfaceForm({
  951. 'device': device.pk,
  952. 'name': name,
  953. 'form_factor': form.cleaned_data['form_factor'],
  954. 'mgmt_only': form.cleaned_data['mgmt_only'],
  955. 'description': form.cleaned_data['description'],
  956. })
  957. if iface_form.is_valid():
  958. interfaces.append(iface_form.save(commit=False))
  959. else:
  960. form.add_error('name_pattern', "Duplicate interface name for this device: {}".format(name))
  961. if not form.errors:
  962. Interface.objects.bulk_create(interfaces)
  963. messages.success(request, "Added {} interface(s) to {}".format(len(interfaces), device))
  964. if '_addanother' in request.POST:
  965. return redirect('dcim:interface_add', pk=device.pk)
  966. else:
  967. return redirect('dcim:device', pk=device.pk)
  968. else:
  969. form = forms.InterfaceCreateForm()
  970. return render(request, 'dcim/interface_edit.html', {
  971. 'device': device,
  972. 'form': form,
  973. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  974. })
  975. @permission_required('dcim.change_interface')
  976. def interface_edit(request, pk):
  977. interface = get_object_or_404(Interface, pk=pk)
  978. if request.method == 'POST':
  979. form = forms.InterfaceForm(request.POST, instance=interface)
  980. if form.is_valid():
  981. interface = form.save()
  982. messages.success(request, "Modified {0} interface {1}".format(interface.device.name, interface.name))
  983. return redirect('dcim:device', pk=interface.device.pk)
  984. else:
  985. form = forms.InterfaceForm(instance=interface)
  986. return render(request, 'dcim/interface_edit.html', {
  987. 'interface': interface,
  988. 'form': form,
  989. 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}),
  990. })
  991. @permission_required('dcim.delete_interface')
  992. def interface_delete(request, pk):
  993. interface = get_object_or_404(Interface, pk=pk)
  994. if request.method == 'POST':
  995. form = ConfirmationForm(request.POST)
  996. if form.is_valid():
  997. interface.delete()
  998. messages.success(request, "Interface {0} has been deleted from {1}".format(interface, interface.device))
  999. return redirect('dcim:device', pk=interface.device.pk)
  1000. else:
  1001. form = ConfirmationForm()
  1002. return render(request, 'dcim/interface_delete.html', {
  1003. 'interface': interface,
  1004. 'form': form,
  1005. 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}),
  1006. })
  1007. class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
  1008. permission_required = 'dcim.add_interface'
  1009. cls = Device
  1010. form = forms.InterfaceBulkCreateForm
  1011. template_name = 'dcim/interface_bulk_add.html'
  1012. default_redirect_url = 'dcim:device_list'
  1013. def update_objects(self, pk_list, form):
  1014. selected_devices = Device.objects.filter(pk__in=pk_list)
  1015. interfaces = []
  1016. for device in selected_devices:
  1017. for name in form.cleaned_data['name_pattern']:
  1018. iface_form = forms.InterfaceForm({
  1019. 'device': device.pk,
  1020. 'name': name,
  1021. 'form_factor': form.cleaned_data['form_factor'],
  1022. 'mgmt_only': form.cleaned_data['mgmt_only'],
  1023. 'description': form.cleaned_data['description'],
  1024. })
  1025. if iface_form.is_valid():
  1026. interfaces.append(iface_form.save(commit=False))
  1027. else:
  1028. form.add_error(None, "Duplicate interface {} found for device {}".format(name, device))
  1029. if not form.errors:
  1030. Interface.objects.bulk_create(interfaces)
  1031. messages.success(self.request, "Added {} interfaces to {} devices".format(len(interfaces),
  1032. len(selected_devices)))
  1033. #
  1034. # Device bays
  1035. #
  1036. @permission_required('dcim.add_devicebay')
  1037. def devicebay_add(request, pk):
  1038. device = get_object_or_404(Device, pk=pk)
  1039. if request.method == 'POST':
  1040. form = forms.DeviceBayCreateForm(request.POST)
  1041. if form.is_valid():
  1042. device_bays = []
  1043. for name in form.cleaned_data['name_pattern']:
  1044. devicebay_form = forms.DeviceBayForm({
  1045. 'device': device.pk,
  1046. 'name': name,
  1047. })
  1048. if devicebay_form.is_valid():
  1049. device_bays.append(devicebay_form.save(commit=False))
  1050. else:
  1051. for err in devicebay_form.errors.get('__all__', []):
  1052. form.add_error('name_pattern', err)
  1053. if not form.errors:
  1054. DeviceBay.objects.bulk_create(device_bays)
  1055. messages.success(request, "Added {} device bay(s) to {}".format(len(device_bays), device))
  1056. if '_addanother' in request.POST:
  1057. return redirect('dcim:devicebay_add', pk=device.pk)
  1058. else:
  1059. return redirect('dcim:device', pk=device.pk)
  1060. else:
  1061. form = forms.DeviceBayCreateForm()
  1062. return render(request, 'dcim/devicebay_edit.html', {
  1063. 'device': device,
  1064. 'form': form,
  1065. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1066. })
  1067. @permission_required('dcim.change_devicebay')
  1068. def devicebay_edit(request, pk):
  1069. devicebay = get_object_or_404(DeviceBay, pk=pk)
  1070. if request.method == 'POST':
  1071. form = forms.DeviceBayForm(request.POST, instance=devicebay)
  1072. if form.is_valid():
  1073. devicebay = form.save()
  1074. messages.success(request, "Modified {} bay {}".format(devicebay.device.name, devicebay.name))
  1075. return redirect('dcim:device', pk=devicebay.device.pk)
  1076. else:
  1077. form = forms.DeviceBayForm(instance=devicebay)
  1078. return render(request, 'dcim/devicebay_edit.html', {
  1079. 'devicebay': devicebay,
  1080. 'form': form,
  1081. 'cancel_url': reverse('dcim:device', kwargs={'pk': devicebay.device.pk}),
  1082. })
  1083. @permission_required('dcim.delete_devicebay')
  1084. def devicebay_delete(request, pk):
  1085. devicebay = get_object_or_404(DeviceBay, pk=pk)
  1086. if request.method == 'POST':
  1087. form = ConfirmationForm(request.POST)
  1088. if form.is_valid():
  1089. devicebay.delete()
  1090. messages.success(request, "Device bay {} has been deleted from {}".format(devicebay, devicebay.device))
  1091. return redirect('dcim:device', pk=devicebay.device.pk)
  1092. else:
  1093. form = ConfirmationForm()
  1094. return render(request, 'dcim/devicebay_delete.html', {
  1095. 'devicebay': devicebay,
  1096. 'form': form,
  1097. 'cancel_url': reverse('dcim:device', kwargs={'pk': devicebay.device.pk}),
  1098. })
  1099. @permission_required('dcim.change_devicebay')
  1100. def devicebay_populate(request, pk):
  1101. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1102. if request.method == 'POST':
  1103. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  1104. if form.is_valid():
  1105. device_bay.installed_device = form.cleaned_data['installed_device']
  1106. device_bay.save()
  1107. if not form.errors:
  1108. messages.success(request, "Added {} to {}".format(device_bay.installed_device, device_bay))
  1109. return redirect('dcim:device', pk=device_bay.device.pk)
  1110. else:
  1111. form = forms.PopulateDeviceBayForm(device_bay)
  1112. return render(request, 'dcim/devicebay_populate.html', {
  1113. 'device_bay': device_bay,
  1114. 'form': form,
  1115. 'cancel_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1116. })
  1117. @permission_required('dcim.change_devicebay')
  1118. def devicebay_depopulate(request, pk):
  1119. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1120. if request.method == 'POST':
  1121. form = ConfirmationForm(request.POST)
  1122. if form.is_valid():
  1123. removed_device = device_bay.installed_device
  1124. device_bay.installed_device = None
  1125. device_bay.save()
  1126. messages.success(request, "{} has been removed from {}".format(removed_device, device_bay))
  1127. return redirect('dcim:device', pk=device_bay.device.pk)
  1128. else:
  1129. form = ConfirmationForm()
  1130. return render(request, 'dcim/devicebay_depopulate.html', {
  1131. 'device_bay': device_bay,
  1132. 'form': form,
  1133. 'cancel_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1134. })
  1135. #
  1136. # Interface connections
  1137. #
  1138. @permission_required('dcim.add_interfaceconnection')
  1139. def interfaceconnection_add(request, pk):
  1140. device = get_object_or_404(Device, pk=pk)
  1141. if request.method == 'POST':
  1142. form = forms.InterfaceConnectionForm(device, request.POST)
  1143. if form.is_valid():
  1144. interfaceconnection = form.save()
  1145. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  1146. interfaceconnection.interface_a.device,
  1147. interfaceconnection.interface_a,
  1148. interfaceconnection.interface_b.device,
  1149. interfaceconnection.interface_b,
  1150. ))
  1151. if '_addanother' in request.POST:
  1152. base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
  1153. params = urlencode({
  1154. 'rack_b': interfaceconnection.interface_b.device.rack.pk,
  1155. 'device_b': interfaceconnection.interface_b.device.pk,
  1156. })
  1157. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  1158. else:
  1159. return redirect('dcim:device', pk=device.pk)
  1160. else:
  1161. form = forms.InterfaceConnectionForm(device, initial={
  1162. 'interface_a': request.GET.get('interface', None),
  1163. 'rack_b': request.GET.get('rack_b', None),
  1164. 'device_b': request.GET.get('device_b', None),
  1165. })
  1166. return render(request, 'dcim/interfaceconnection_edit.html', {
  1167. 'device': device,
  1168. 'form': form,
  1169. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1170. })
  1171. @permission_required('dcim.delete_interfaceconnection')
  1172. def interfaceconnection_delete(request, pk):
  1173. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1174. device_id = request.GET.get('device', None)
  1175. if request.method == 'POST':
  1176. form = forms.InterfaceConnectionDeletionForm(request.POST)
  1177. if form.is_valid():
  1178. interfaceconnection.delete()
  1179. messages.success(request, "Deleted the connection between {0} {1} and {2} {3}".format(
  1180. interfaceconnection.interface_a.device,
  1181. interfaceconnection.interface_a,
  1182. interfaceconnection.interface_b.device,
  1183. interfaceconnection.interface_b,
  1184. ))
  1185. if form.cleaned_data['device']:
  1186. return redirect('dcim:device', pk=form.cleaned_data['device'].pk)
  1187. else:
  1188. return redirect('dcim:device_list')
  1189. else:
  1190. form = forms.InterfaceConnectionDeletionForm(initial={
  1191. 'device': device_id,
  1192. })
  1193. # Determine where to direct user upon cancellation
  1194. if device_id:
  1195. cancel_url = reverse('dcim:device', kwargs={'pk': device_id})
  1196. else:
  1197. cancel_url = reverse('dcim:device_list')
  1198. return render(request, 'dcim/interfaceconnection_delete.html', {
  1199. 'interfaceconnection': interfaceconnection,
  1200. 'device_id': device_id,
  1201. 'form': form,
  1202. 'cancel_url': cancel_url,
  1203. })
  1204. class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1205. permission_required = 'dcim.change_interface'
  1206. form = forms.InterfaceConnectionImportForm
  1207. table = tables.InterfaceConnectionTable
  1208. template_name = 'dcim/interface_connections_import.html'
  1209. #
  1210. # Connections
  1211. #
  1212. class ConsoleConnectionsListView(ObjectListView):
  1213. queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\
  1214. .order_by('cs_port__device__name', 'cs_port__name')
  1215. filter = filters.ConsoleConnectionFilter
  1216. filter_form = forms.ConsoleConnectionFilterForm
  1217. table = tables.ConsoleConnectionTable
  1218. template_name = 'dcim/console_connections_list.html'
  1219. class PowerConnectionsListView(ObjectListView):
  1220. queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\
  1221. .order_by('power_outlet__device__name', 'power_outlet__name')
  1222. filter = filters.PowerConnectionFilter
  1223. filter_form = forms.PowerConnectionFilterForm
  1224. table = tables.PowerConnectionTable
  1225. template_name = 'dcim/power_connections_list.html'
  1226. class InterfaceConnectionsListView(ObjectListView):
  1227. queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\
  1228. .order_by('interface_a__device__name', 'interface_a__name')
  1229. filter = filters.InterfaceConnectionFilter
  1230. filter_form = forms.InterfaceConnectionFilterForm
  1231. table = tables.InterfaceConnectionTable
  1232. template_name = 'dcim/interface_connections_list.html'
  1233. #
  1234. # IP addresses
  1235. #
  1236. @permission_required('ipam.add_ipaddress')
  1237. def ipaddress_assign(request, pk):
  1238. device = get_object_or_404(Device, pk=pk)
  1239. if request.method == 'POST':
  1240. form = forms.IPAddressForm(device, request.POST)
  1241. if form.is_valid():
  1242. ipaddress = form.save(commit=False)
  1243. ipaddress.interface = form.cleaned_data['interface']
  1244. ipaddress.save()
  1245. messages.success(request, "Added new IP address {0} to interface {1}".format(ipaddress,
  1246. ipaddress.interface))
  1247. if form.cleaned_data['set_as_primary']:
  1248. device.primary_ip = ipaddress
  1249. device.save()
  1250. if '_addanother' in request.POST:
  1251. return redirect('dcim:ipaddress_assign', pk=device.pk)
  1252. else:
  1253. return redirect('dcim:device', pk=device.pk)
  1254. else:
  1255. form = forms.IPAddressForm(device)
  1256. return render(request, 'dcim/ipaddress_assign.html', {
  1257. 'device': device,
  1258. 'form': form,
  1259. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1260. })
  1261. #
  1262. # Modules
  1263. #
  1264. @permission_required('dcim.add_module')
  1265. def module_add(request, pk):
  1266. device = get_object_or_404(Device, pk=pk)
  1267. if request.method == 'POST':
  1268. form = forms.ModuleForm(request.POST)
  1269. if form.is_valid():
  1270. module = form.save(commit=False)
  1271. module.device = device
  1272. module.save()
  1273. messages.success(request, "Added module {} to {}".format(module.name, module.device.name))
  1274. if '_addanother' in request.POST:
  1275. return redirect('dcim:module_add', pk=module.device.pk)
  1276. else:
  1277. return redirect('dcim:device_inventory', pk=module.device.pk)
  1278. else:
  1279. form = forms.ModuleForm()
  1280. return render(request, 'dcim/module_edit.html', {
  1281. 'device': device,
  1282. 'form': form,
  1283. 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': device.pk}),
  1284. })
  1285. @permission_required('dcim.change_module')
  1286. def module_edit(request, pk):
  1287. module = get_object_or_404(Module, pk=pk)
  1288. if request.method == 'POST':
  1289. form = forms.ModuleForm(request.POST, instance=module)
  1290. if form.is_valid():
  1291. module = form.save()
  1292. messages.success(request, "Modified {} module {}".format(module.device.name, module.name))
  1293. return redirect('dcim:device_inventory', pk=module.device.pk)
  1294. else:
  1295. form = forms.ModuleForm(instance=module)
  1296. return render(request, 'dcim/module_edit.html', {
  1297. 'module': module,
  1298. 'form': form,
  1299. 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': module.device.pk}),
  1300. })
  1301. @permission_required('dcim.delete_module')
  1302. def module_delete(request, pk):
  1303. module = get_object_or_404(Module, pk=pk)
  1304. if request.method == 'POST':
  1305. form = ConfirmationForm(request.POST)
  1306. if form.is_valid():
  1307. module.delete()
  1308. messages.success(request, "Module {} has been deleted from {}".format(module, module.device))
  1309. return redirect('dcim:device_inventory', pk=module.device.pk)
  1310. else:
  1311. form = ConfirmationForm()
  1312. return render(request, 'dcim/module_delete.html', {
  1313. 'module': module,
  1314. 'form': form,
  1315. 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': module.device.pk}),
  1316. })