admin.py 30 KB

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