admin.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. from django.contrib import admin
  2. from django.db import models
  3. from django.db.models import Q
  4. from django.forms import ModelForm, BaseInlineFormSet
  5. from django.utils import timezone
  6. from django.urls import reverse
  7. from django.utils.html import format_html
  8. from django.conf.urls import url
  9. from django.template.response import TemplateResponse
  10. from django.core.serializers import serialize
  11. from django.http import HttpResponse
  12. from django.db.models.functions import Cast
  13. from django.contrib.postgres.aggregates import StringAgg
  14. from django.db import connection
  15. from django.core.cache import cache
  16. from django.contrib.humanize.templatetags.humanize import naturaltime
  17. from djgeojson.views import GeoJSONLayerView
  18. from urllib.parse import urlencode
  19. from functools import partial, update_wrapper
  20. from datetime import timedelta
  21. from djadhere.utils import get_active_filter
  22. from adhesions.models import Adhesion
  23. from banking.models import PaymentUpdate
  24. from .models import Service, ServiceType, IPPrefix, IPResource, Route, Tunnel, \
  25. ServiceAllocation, Antenna, AntennaAllocation, Allocation, \
  26. Switch, Port
  27. from .utils.notifications import notify_allocation
  28. from .forms import AntennaForm
  29. ### Filters
  30. class ResourceInUseFilter(admin.SimpleListFilter):
  31. title = 'disponibilité'
  32. parameter_name = 'available'
  33. def lookups(self, request, model_admin):
  34. return (
  35. (1, 'Disponible'),
  36. (0, 'Non disponible'),
  37. )
  38. def queryset(self, request, queryset):
  39. available_filter = Q(reserved=False, in_use=False)
  40. if self.value() == '0': # non disponible
  41. return queryset.exclude(available_filter)
  42. if self.value() == '1': # disponible
  43. return queryset.filter(available_filter)
  44. class ResourcePingFilter(admin.SimpleListFilter):
  45. title = 'ping'
  46. parameter_name = 'ping'
  47. def lookups(self, request, model_admin):
  48. return (
  49. ('up', 'UP'),
  50. ('down', 'DOWN'),
  51. ('down-since', 'DOWN depuis…'),
  52. ('never-up', 'Jamais vu UP'),
  53. )
  54. def queryset(self, request, queryset):
  55. if self.value() == 'up':
  56. return queryset.filter(up=True)
  57. if self.value() == 'down':
  58. return queryset.filter(models.Q(up__isnull=True) | models.Q(up=False))
  59. if self.value() == 'down-since':
  60. return queryset.filter(up=False)
  61. if self.value() == 'never-up':
  62. return queryset.filter(up__isnull=True)
  63. class ActiveServiceFilter(admin.SimpleListFilter):
  64. title = 'actif'
  65. parameter_name = 'active'
  66. def lookups(self, request, model_admin):
  67. return (
  68. (1, 'Actif'),
  69. (0, 'Inactif'),
  70. )
  71. def queryset(self, request, queryset):
  72. if self.value() == '0': # inactif
  73. return queryset.exclude(get_active_filter('allocation'))
  74. if self.value() == '1': # actif
  75. return queryset.filter(get_active_filter('allocation'))
  76. class RouteFilter(admin.SimpleListFilter):
  77. title = 'route'
  78. parameter_name = 'route'
  79. def lookups(self, request, model_admin):
  80. return ServiceAllocation.objects.filter(active=True).values_list('route__pk', 'route__name').distinct()
  81. def queryset(self, request, queryset):
  82. try:
  83. route = int(self.value())
  84. except (TypeError, ValueError):
  85. pass
  86. else:
  87. allocations = ServiceAllocation.objects.filter(active=True, route__pk=route).values_list('pk', flat=True)
  88. queryset = queryset.filter(service_allocation__in=allocations)
  89. return queryset
  90. class AntennaPrefixFilter(admin.SimpleListFilter):
  91. title = 'préfix'
  92. parameter_name = 'prefix'
  93. def lookups(self, request, model_admin):
  94. resources = AntennaAllocation.objects.filter(active=True).values_list('resource__pk', flat=True)
  95. prefixes = IPPrefix.objects.filter(ipresource__in=resources).values_list('pk', 'prefix').distinct()
  96. return prefixes
  97. def queryset(self, request, queryset):
  98. try:
  99. prefix = int(self.value())
  100. except (TypeError, ValueError):
  101. pass
  102. else:
  103. allocations = AntennaAllocation.objects.filter(active=True, resource__prefixes__pk=prefix).values_list('pk', flat=True)
  104. queryset = queryset.filter(allocation__in=allocations)
  105. return queryset
  106. class AntennaPositionFilter(admin.SimpleListFilter):
  107. title = 'géolocalisation'
  108. parameter_name = 'position'
  109. def lookups(self, request, model_admin):
  110. return (
  111. ('1', 'Connue'),
  112. ('0', 'Inconnue'),
  113. )
  114. def queryset(self, request, queryset):
  115. query = Q(position__isnull=True)
  116. if self.value() == '0':
  117. return queryset.filter(query)
  118. if self.value() == '1':
  119. return queryset.exclude(query)
  120. return queryset
  121. class ActiveTunnelFilter(admin.SimpleListFilter):
  122. title = 'status'
  123. parameter_name = 'active'
  124. def lookups(self, request, model_admin):
  125. return (
  126. ('1', 'Actif'),
  127. ('0', 'Désactivé'),
  128. )
  129. def queryset(self, request, queryset):
  130. query = Q(ended__isnull=True)
  131. if self.value() == '0':
  132. return queryset.exclude(query)
  133. if self.value() == '1':
  134. return queryset.filter(query)
  135. return queryset
  136. ### Inlines
  137. class AllocationInlineFormSet(BaseInlineFormSet):
  138. def save_new(self, form, commit=True):
  139. obj = super().save_new(form, commit)
  140. if type(obj) == ServiceAllocation:
  141. notify_allocation(self.request, obj)
  142. return obj
  143. def save_existing(self, form, instance, commit=True):
  144. old = type(instance).objects.get(pk=instance.pk)
  145. if type(instance) == ServiceAllocation:
  146. notify_allocation(self.request, instance, old)
  147. return super().save_existing(form, instance, commit)
  148. class AllocationInline(admin.TabularInline):
  149. formset = AllocationInlineFormSet
  150. extra = 0
  151. show_change_link = True
  152. def get_queryset(self, request):
  153. qs = super().get_queryset(request)
  154. qs = qs.select_related('resource')
  155. return qs
  156. def get_formset(self, request, obj=None, **kwargs):
  157. formset = super().get_formset(request, obj, **kwargs)
  158. formset.request = request
  159. return formset
  160. def has_delete_permission(self, request, obj=None):
  161. return False
  162. class ActiveAllocationMixin:
  163. verbose_name_plural = 'Allocations actives'
  164. def get_max_num(self, request, obj=None, **kwargs):
  165. existing = obj.allocations.count() if obj else 0
  166. # pour simplifier la validation, on ajoute qu’une allocation à la fois
  167. # il faudrait surcharger la méthode clean du formset pour supprimer cette limite
  168. return existing + 1
  169. def get_queryset(self, request):
  170. return super().get_queryset(request).filter(get_active_filter())
  171. class InactiveAllocationMixin:
  172. verbose_name_plural = 'Anciennes allocations'
  173. max_num = 0
  174. def get_queryset(self, request):
  175. return super().get_queryset(request).exclude(get_active_filter())
  176. class ServiceAllocationMixin:
  177. model = ServiceAllocation
  178. fields = ('id', 'service', 'resource', 'route', 'start', 'end')
  179. raw_id_fields = ('resource',)
  180. autocomplete_fields = ('service',)
  181. def get_queryset(self, request):
  182. qs = super().get_queryset(request)
  183. qs = qs.select_related('route')
  184. return qs
  185. class AntennaAllocationMixin:
  186. model = AntennaAllocation
  187. fields = ('id', 'antenna', 'resource', 'start', 'end')
  188. raw_id_fields = ('resource',)
  189. autocomplete_fields = ('antenna',)
  190. def get_queryset(self, request):
  191. qs = super().get_queryset(request)
  192. qs = qs.select_related('antenna')
  193. return qs
  194. class ActiveServiceAllocationInline(ServiceAllocationMixin, ActiveAllocationMixin, AllocationInline):
  195. pass
  196. class InactiveServiceAllocationInline(ServiceAllocationMixin, InactiveAllocationMixin, AllocationInline):
  197. pass
  198. class ActiveAntennaAllocationInline(AntennaAllocationMixin, ActiveAllocationMixin, AllocationInline):
  199. pass
  200. class InactiveAntennaAllocationInline(AntennaAllocationMixin, InactiveAllocationMixin, AllocationInline):
  201. pass
  202. class PortInline(admin.TabularInline):
  203. model = Port
  204. max_num = 0
  205. def has_add_permission(self, request):
  206. return False
  207. def has_delete_permission(self, request, obj=None):
  208. return False
  209. class SwitchPortInline(PortInline):
  210. fields = ('port', 'up', 'reserved', 'service', 'notes',)
  211. readonly_fields = ('port', 'up',)
  212. autocomplete_fields = ('service',)
  213. def get_queryset(self, request):
  214. qs = super().get_queryset(request)
  215. qs = qs.select_related('switch', 'service', 'service__service_type')
  216. return qs
  217. class ServicePortInline(PortInline):
  218. fields = ('switch', 'port', 'up', 'notes',)
  219. readonly_fields = ('switch', 'port', 'up',)
  220. ### Actions
  221. def ends_resource(resource, request, queryset):
  222. now = timezone.now()
  223. queryset.exclude(start__lte=now, end__isnull=False).update(end=now)
  224. # TODO: send mail
  225. ends_resource.short_description = 'Terminer les allocations sélectionnées'
  226. ### ModelAdmin
  227. class ServiceAdmin(admin.ModelAdmin):
  228. list_display = ('id', 'get_adhesion_link', 'get_adherent_link', 'service_type', 'label', 'is_active',)
  229. list_select_related = ('adhesion', 'adhesion__user', 'adhesion__user__profile', 'adhesion__corporation', 'service_type',)
  230. list_filter = (
  231. ActiveServiceFilter,
  232. 'loan_equipment',
  233. ('service_type', admin.RelatedOnlyFieldListFilter),
  234. )
  235. search_fields = ('=id', 'service_type__name', 'label', 'notes',)
  236. fields = ('adhesion', 'service_type', 'label', 'notes', 'loan_equipment', 'get_contribution_link', 'is_active',)
  237. readonly_fields = ('get_contribution_link', 'is_active',)
  238. raw_id_fields = ('adhesion',)
  239. def get_queryset(self, request):
  240. qs = super().get_queryset(request)
  241. qs = qs.prefetch_related('allocations',)
  242. return qs
  243. get_adhesion_link = lambda self, service: service.adhesion.get_adhesion_link()
  244. get_adhesion_link.short_description = Adhesion.get_adhesion_link.short_description
  245. get_adherent_link = lambda self, service: service.adhesion.get_adherent_link()
  246. get_adherent_link.short_description = Adhesion.get_adherent_link.short_description
  247. def get_contribution_link(self, obj):
  248. return format_html(u'<a href="{}">{}</a>', obj.contribution.get_absolute_url(), obj.contribution.get_current_payment_display())
  249. get_contribution_link.short_description = 'Contribution financière'
  250. def get_inline_instances(self, request, obj=None):
  251. inlines = []
  252. if obj and obj.ports.exists():
  253. inlines += [ServicePortInline]
  254. inlines += [ActiveServiceAllocationInline, InactiveServiceAllocationInline]
  255. return [inline(self.model, self.admin_site) for inline in inlines]
  256. def get_actions(self, request):
  257. actions = super().get_actions(request)
  258. if 'delete_selected' in actions:
  259. del actions['delete_selected']
  260. return actions
  261. def has_delete_permission(self, request, obj=None):
  262. if not obj:
  263. return False
  264. one_year_ago = timezone.now() - timedelta(days=365)
  265. contribution = obj.contribution.updates.filter(validated=True).first()
  266. # s’il y avait un paiement actif il y a moins d’un an
  267. if not contribution or contribution.payment_method != PaymentUpdate.STOP or contribution.start > one_year_ago:
  268. return False
  269. # s’il y avait une allocation active il y a moins d’un an
  270. if any(map(lambda a: a.end is None or a.end > one_year_ago, obj.allocations.all())):
  271. return False
  272. return True
  273. class IPPrefixAdmin(admin.ModelAdmin):
  274. readonly_fields = ('prefix',)
  275. def has_delete_permission(self, request, obj=None):
  276. # Interdiction de supprimer le préfix s’il est assigné à une route
  277. return obj and obj.tunnel_set.exists()
  278. # pour embêcher de by-passer le check has_delete_permission, on désactive l’action delete
  279. def get_actions(self, request):
  280. actions = super().get_actions(request)
  281. if 'delete_selected' in actions:
  282. del actions['delete_selected']
  283. return actions
  284. class IPResourceAdmin(admin.ModelAdmin):
  285. list_display = ('__str__', 'available_display', 'last_use', 'ping',)
  286. list_filter = (
  287. 'category',
  288. ResourceInUseFilter,
  289. ResourcePingFilter,
  290. 'reserved',
  291. ('prefixes', admin.RelatedOnlyFieldListFilter),
  292. RouteFilter,
  293. )
  294. search_fields = ('=ip', 'notes',)
  295. def get_fields(self, request, obj=None):
  296. return self.get_readonly_fields(request, obj)
  297. def get_readonly_fields(self, request, obj=None):
  298. fields = ['ip']
  299. if obj and obj.reserved:
  300. fields += ['reserved']
  301. if obj and not obj.in_use:
  302. fields += ['last_use']
  303. if obj and obj.last_time_up and obj.last_check:
  304. fields += ['last_time_up', 'last_check']
  305. if obj and obj.checkmk_label:
  306. fields += ['checkmk']
  307. if obj and obj.notes:
  308. fields += ['notes']
  309. return fields
  310. def get_inline_instances(self, request, obj=None):
  311. if obj:
  312. if obj.category == 0:
  313. inlines = (ActiveServiceAllocationInline, InactiveServiceAllocationInline,)
  314. elif obj.category == 1:
  315. inlines = (ActiveAntennaAllocationInline, InactiveAntennaAllocationInline,)
  316. else:
  317. inlines = ()
  318. return [inline(self.model, self.admin_site) for inline in inlines]
  319. def get_queryset(self, request):
  320. qs = super().get_queryset(request)
  321. now = timezone.now()
  322. qs = qs.annotate(
  323. last_use=models.Case(
  324. models.When(in_use=True, then=now),
  325. models.When(category=0, then=models.Max('service_allocation__end')),
  326. models.When(category=1, then=models.Max('antenna_allocation__end')),
  327. default=None,
  328. ))
  329. qs = qs.annotate(
  330. up=models.Case(
  331. models.When(last_check__isnull=False, last_time_up__isnull=False, last_time_up=models.F('last_check'), then=True),
  332. models.When(last_check__isnull=False, last_time_up__isnull=False, then=False),
  333. default=None,
  334. output_field=models.NullBooleanField(),
  335. ))
  336. qs = qs.annotate(downtime=models.F('last_check') - models.F('last_time_up'))
  337. return qs
  338. def available_display(self, obj):
  339. return not obj.reserved and not obj.in_use
  340. available_display.short_description = 'Disponible'
  341. available_display.boolean = True
  342. def last_use(self, obj):
  343. if obj.last_use:
  344. return naturaltime(obj.last_use)
  345. else:
  346. return '-'
  347. last_use.short_description = 'Dernière utilisation'
  348. last_use.admin_order_field = 'last_use'
  349. def ping(self, obj):
  350. if obj.up:
  351. label = 'UP'
  352. elif obj.last_time_up:
  353. label = 'dernier ping : ' + naturaltime(obj.last_time_up)
  354. else:
  355. label = 'DOWN'
  356. if obj.checkmk_url:
  357. return format_html('<a href="{}">{}</a>', obj.checkmk_url, label)
  358. else:
  359. return label
  360. ping.short_description = 'ping'
  361. ping.admin_order_field = 'downtime'
  362. def checkmk(self, obj):
  363. return format_html('<a href="{}">{}</a>', obj.checkmk_url, 'voir')
  364. checkmk.short_description = 'CheckMK'
  365. def get_actions(self, request):
  366. actions = super().get_actions(request)
  367. if 'delete_selected' in actions:
  368. del actions['delete_selected']
  369. return actions
  370. def has_add_permission(self, request, obj=None):
  371. return False
  372. def has_delete_permission(self, request, obj=None):
  373. return False
  374. class RouteAdmin(admin.ModelAdmin):
  375. list_display = ('name',)
  376. search_fields = ('name',)
  377. def get_fieldsets(self, request, obj=None):
  378. if obj:
  379. return (
  380. (None, {'fields': ['name']}),
  381. ('Adhérent·e·s', {'fields': ['get_adh'], 'classes': ['collapse']}),
  382. ('E-mails', {'fields': ['get_email'], 'classes': ['collapse']}),
  383. ('SMS', {'fields': ['get_sms'], 'classes': ['collapse']}),
  384. ('IP', {'fields': ['get_ip'], 'classes': ['collapse']}),
  385. )
  386. else:
  387. return (
  388. (None, {'fields': ['name']}),
  389. )
  390. def get_readonly_fields(self, request, obj=None):
  391. if obj:
  392. return ('get_email', 'get_sms', 'get_ip', 'get_adh',)
  393. else:
  394. return ()
  395. def get_email(self, route):
  396. return '\n'.join(route.get_email())
  397. get_email.short_description = 'E-mails'
  398. def get_sms(self, route):
  399. sms_filter = lambda x: x[:2] == '06' or x[:2] == '07' or x[:3] == '+336' or x[:3] == '+337'
  400. return '\n'.join(filter(sms_filter, route.get_tel()))
  401. get_sms.short_description = 'SMS'
  402. def get_ip(self, route):
  403. return '\n'.join(route.get_ip())
  404. get_ip.short_description = 'IP'
  405. def get_adh(self, route):
  406. return '\n'.join(map(lambda adh: '%s %s' % (adh, adh.adherent), route.get_adh()))
  407. get_adh.short_description = 'Adhérent·e·s'
  408. def get_actions(self, request):
  409. actions = super().get_actions(request)
  410. if 'delete_selected' in actions:
  411. del actions['delete_selected']
  412. return actions
  413. def has_delete_permission(self, request, obj=None):
  414. return False
  415. class TunnelAdmin(admin.ModelAdmin):
  416. list_display = ('name', 'description', 'created', 'active')
  417. list_filter = (
  418. ActiveTunnelFilter,
  419. )
  420. def active(self, obj):
  421. return not obj.ended
  422. active.short_description = 'Actif'
  423. active.boolean = True
  424. class ServiceTypeAdmin(admin.ModelAdmin):
  425. fields = ('name',)
  426. def get_actions(self, request):
  427. actions = super().get_actions(request)
  428. if 'delete_selected' in actions:
  429. del actions['delete_selected']
  430. return actions
  431. def has_delete_permission(self, request, obj=None):
  432. return False
  433. class ActiveAntennaLayer(GeoJSONLayerView):
  434. def get_queryset(self):
  435. return Antenna.objects.filter(get_active_filter('allocation'))
  436. class AntennaAdmin(admin.ModelAdmin):
  437. inlines = (ActiveAntennaAllocationInline, InactiveAntennaAllocationInline,)
  438. list_filter = (
  439. AntennaPrefixFilter,
  440. AntennaPositionFilter,
  441. 'mode',
  442. 'ssid',
  443. )
  444. list_display_links = ('id', 'label')
  445. search_fields = ('=id', 'label', 'notes', 'ssid')
  446. raw_id_fields = ('contact',)
  447. form = AntennaForm
  448. def get_queryset(self, request):
  449. qs = super().get_queryset(request)
  450. if connection.vendor == 'postgresql':
  451. qs = qs.annotate(
  452. ip=StringAgg( # concaténation des IP avec des virgules directement par postgresql
  453. Cast( # casting en TextField car StringApp oppère sur des string mais les ip sont des inet
  454. models.Case( # seulement les IP des allocations actives
  455. models.When(
  456. get_active_filter('allocation'),
  457. then='allocation__resource__ip'
  458. ),
  459. ),
  460. models.TextField()
  461. ),
  462. delimiter=', '
  463. )
  464. )
  465. return qs
  466. def get_list_display(self, request):
  467. # ssid_display needs request to access query string and preserve filters
  468. ssid_display = partial(self.ssid_display, request=request)
  469. update_wrapper(ssid_display, self.ssid_display)
  470. return ('id', 'label', 'mode', ssid_display, 'position_display', 'ip_display')
  471. def position_display(self, obj):
  472. return obj.position is not None
  473. position_display.short_description = 'Géolocalisé'
  474. position_display.boolean = True
  475. def ip_display(self, obj):
  476. if connection.vendor == 'postgresql':
  477. return obj.ip
  478. else:
  479. # peu efficace car génère une requête par ligne
  480. allocations = obj.allocations.filter(active=True)
  481. return ', '.join(allocations.values_list('resource__ip', flat=True)) or '-'
  482. ip_display.short_description = 'IP'
  483. def ssid_display(self, obj, request):
  484. if obj.ssid:
  485. qs = request.GET.copy()
  486. qs.update({'ssid': obj.ssid})
  487. ssid_url = reverse('admin:services_antenna_changelist') + '?' + urlencode(qs)
  488. return format_html(u'<a href="{}">{}</a>', ssid_url, obj.ssid)
  489. else:
  490. return None
  491. ssid_display.short_description = 'SSID'
  492. def get_actions(self, request):
  493. actions = super().get_actions(request)
  494. if 'delete_selected' in actions:
  495. del actions['delete_selected']
  496. return actions
  497. def has_delete_permission(self, request, obj=None):
  498. return False
  499. def get_urls(self):
  500. my_urls = [
  501. url(r'^map/$', self.admin_site.admin_view(self.map_view, cacheable=True), name='antenna-map'),
  502. url(r'^map/data.json$', self.admin_site.admin_view(ActiveAntennaLayer.as_view(
  503. model=Antenna,
  504. geometry_field='position',
  505. properties=('label', 'mode', 'ssid', 'orientation', 'absolute_url',),
  506. )), name='antenna-map-data'),
  507. ]
  508. return my_urls + super().get_urls()
  509. def map_view(self, request):
  510. return TemplateResponse(request, 'services/antenna_map.html', dict(
  511. self.admin_site.each_context(request),
  512. opts=self.model._meta,
  513. json_url=reverse('admin:antenna-map-data'),
  514. ))
  515. def map_data_view(self, request):
  516. geojson = serialize('geojson', Antenna.objects.all(), geometry_field='point', fields=('position',))
  517. return HttpResponse(geojson, content_type='application/json')
  518. class SwitchAdmin(admin.ModelAdmin):
  519. list_display = ('name', 'ports_count', 'active_ports_count', 'inactive_ports_count', 'unknown_ports_count',)
  520. fields = ('name', 'first_port', 'last_port', 'notes',)
  521. search_fields = ('name', 'notes', 'ports__notes', 'ports__service__label',)
  522. def get_queryset(self, request):
  523. qs = super().get_queryset(request)
  524. qs = qs.annotate(
  525. active_ports_count=models.Count(models.Case(models.When(ports__up=True, then=models.Value('1')))),
  526. unknown_ports_count=models.Count(models.Case(models.When(ports__up__isnull=True, then=models.Value('1')))),
  527. inactive_ports_count=models.Count(models.Case(models.When(ports__up=False, then=models.Value('1')))),
  528. )
  529. return qs
  530. def ports_count(self, switch):
  531. return switch.last_port - switch.first_port + 1
  532. ports_count.short_description = 'Nombre de ports'
  533. def active_ports_count(self, switch):
  534. return switch.active_ports_count
  535. active_ports_count.short_description = 'up'
  536. active_ports_count.admin_order_field = 'active_ports_count'
  537. def inactive_ports_count(self, switch):
  538. return switch.inactive_ports_count
  539. inactive_ports_count.short_description = 'down'
  540. inactive_ports_count.admin_order_field = 'inactive_ports_count'
  541. def unknown_ports_count(self, switch):
  542. return switch.unknown_ports_count
  543. unknown_ports_count.short_description = 'inconnus'
  544. unknown_ports_count.admin_order_field = 'unknown_ports_count'
  545. def get_inline_instances(self, request, obj=None):
  546. if obj:
  547. return [ SwitchPortInline(self.model, self.admin_site) ]
  548. else:
  549. return []
  550. def get_readonly_fields(self, request, obj=None):
  551. if obj:
  552. return ('first_port', 'last_port',)
  553. else:
  554. return ()
  555. def get_actions(self, request):
  556. actions = super().get_actions(request)
  557. if 'delete_selected' in actions:
  558. del actions['delete_selected']
  559. return actions
  560. def has_delete_permission(self, request, obj=None):
  561. return False
  562. admin.site.register(ServiceType, ServiceTypeAdmin)
  563. admin.site.register(Service, ServiceAdmin)
  564. admin.site.register(IPPrefix, IPPrefixAdmin)
  565. admin.site.register(IPResource, IPResourceAdmin)
  566. admin.site.register(Route, RouteAdmin)
  567. admin.site.register(Tunnel, TunnelAdmin)
  568. admin.site.register(Antenna, AntennaAdmin)
  569. admin.site.register(Switch, SwitchAdmin)