admin.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. from django.contrib import admin
  2. from django.contrib.gis import admin as geo_admin
  3. from django.db import models
  4. from django.db.models import Q
  5. from django.forms import ModelForm, BaseInlineFormSet
  6. from django.utils import timezone
  7. from django.core.urlresolvers import reverse
  8. from django.utils.html import format_html
  9. from django.core.mail import mail_managers
  10. from django.conf.urls import url
  11. from django.template.response import TemplateResponse
  12. from django.core.serializers import serialize
  13. from django.http import HttpResponse
  14. from adhesions.models import Adhesion
  15. from .models import Service, ServiceType, IPPrefix, IPResource, Route, Tunnel, ServiceAllocation, Antenna, AntennaAllocation, Allocation
  16. from .utils import notify_allocation
  17. ### Filters
  18. class ResourceInUseFilter(admin.SimpleListFilter):
  19. title = 'disponibilité'
  20. parameter_name = 'available'
  21. def lookups(self, request, model_admin):
  22. return (
  23. (1, 'Disponible'),
  24. (0, 'Non disponible'),
  25. )
  26. def queryset(self, request, queryset):
  27. available_filter = Q(reserved=False, in_use=False)
  28. if self.value() == '0': # non disponible
  29. return queryset.exclude(available_filter)
  30. if self.value() == '1': # disponible
  31. return queryset.filter(available_filter)
  32. class RouteFilter(admin.SimpleListFilter):
  33. title = 'route'
  34. parameter_name = 'route'
  35. def lookups(self, request, model_admin):
  36. return ServiceAllocation.objects.filter(active=True).values_list('route__pk', 'route__name').distinct()
  37. def queryset(self, request, queryset):
  38. try:
  39. route = int(self.value())
  40. except (TypeError, ValueError):
  41. pass
  42. else:
  43. allocations = ServiceAllocation.objects.filter(active=True, route__pk=route).values_list('pk', flat=True)
  44. queryset = queryset.filter(service_allocation__in=allocations)
  45. return queryset
  46. class AntennaPrefixFilter(admin.SimpleListFilter):
  47. title = 'préfix'
  48. parameter_name = 'prefix'
  49. def lookups(self, request, model_admin):
  50. resources = AntennaAllocation.objects.filter(active=True).values_list('resource__pk', flat=True)
  51. prefixes = IPPrefix.objects.filter(ipresource__in=resources).values_list('pk', 'prefix').distinct()
  52. return prefixes
  53. def queryset(self, request, queryset):
  54. try:
  55. prefix = int(self.value())
  56. except (TypeError, ValueError):
  57. pass
  58. else:
  59. allocations = AntennaAllocation.objects.filter(active=True, resource__prefixes__pk=prefix).values_list('pk', flat=True)
  60. queryset = queryset.filter(allocation__in=allocations)
  61. return queryset
  62. class ActiveTunnelFilter(admin.SimpleListFilter):
  63. title = 'status'
  64. parameter_name = 'active'
  65. def lookups(self, request, model_admin):
  66. return (
  67. ('1', 'Actif'),
  68. ('0', 'Désactivé'),
  69. )
  70. def queryset(self, request, queryset):
  71. query = Q(ended__isnull=True)
  72. if self.value() == '0':
  73. return queryset.exclude(query)
  74. if self.value() == '1':
  75. return queryset.filter(query)
  76. return queryset
  77. ### Inlines
  78. class AllocationInlineFormSet(BaseInlineFormSet):
  79. def save_new(self, form, commit=True):
  80. obj = super().save_new(form, commit)
  81. if type(obj) == ServiceAllocation:
  82. notify_allocation(self.request, obj)
  83. return obj
  84. def save_existing(self, form, instance, commit=True):
  85. old = type(instance).objects.get(pk=instance.pk)
  86. if type(instance) == ServiceAllocation:
  87. notify_allocation(self.request, instance, old)
  88. return super().save_existing(form, instance, commit)
  89. class AllocationInline(admin.TabularInline):
  90. formset = AllocationInlineFormSet
  91. extra = 0
  92. verbose_name_plural = 'Allocations'
  93. show_change_link = True
  94. def get_formset(self, request, obj=None, **kwargs):
  95. formset = super().get_formset(request, obj, **kwargs)
  96. formset.request = request
  97. return formset
  98. def get_max_num(self, request, obj=None, **kwargs):
  99. existing = obj.allocations.count() if obj else 0
  100. # pour simplifier la validation, on ajoute qu’une allocation à la fois
  101. # il faudrait surcharger la méthode clean du formset pour supprimer cette limite
  102. return existing + 1
  103. def has_delete_permission(self, request, obj=None):
  104. return False
  105. class ServiceAllocationInline(AllocationInline):
  106. model = ServiceAllocation
  107. fields = ('id', 'service', 'resource', 'route', 'start', 'end')
  108. raw_id_fields = ('service', 'resource',)
  109. class AntennaAllocationInline(AllocationInline):
  110. model = AntennaAllocation
  111. fields = ('id', 'antenna', 'resource', 'start', 'end')
  112. raw_id_fields = ('antenna', 'resource',)
  113. ### Actions
  114. def ends_resource(resource, request, queryset):
  115. now = timezone.now()
  116. queryset.exclude(start__lte=now, end__isnull=False).update(end=now)
  117. # TODO: send mail
  118. ends_resource.short_description = 'Terminer les allocations sélectionnées'
  119. ### ModelAdmin
  120. class ServiceAdmin(admin.ModelAdmin):
  121. list_display = ('id', 'get_adhesion_link', 'get_adherent_link', 'service_type', 'label', 'active')
  122. list_select_related = ('adhesion', 'adhesion__user', 'adhesion__user__profile', 'adhesion__corporation', 'service_type')
  123. list_filter = (
  124. 'active',
  125. ('service_type', admin.RelatedOnlyFieldListFilter),
  126. )
  127. inlines = (ServiceAllocationInline,)
  128. search_fields = ('=id', 'service_type__name', 'label', 'notes',)
  129. raw_id_fields = ('adhesion',)
  130. get_adhesion_link = lambda self, service: service.adhesion.get_adhesion_link()
  131. get_adhesion_link.short_description = Adhesion.get_adhesion_link.short_description
  132. get_adherent_link = lambda self, service: service.adhesion.get_adherent_link()
  133. get_adherent_link.short_description = Adhesion.get_adherent_link.short_description
  134. def get_actions(self, request):
  135. actions = super().get_actions(request)
  136. if 'delete_selected' in actions:
  137. del actions['delete_selected']
  138. return actions
  139. def has_delete_permission(self, request, obj=None):
  140. return False
  141. class IPPrefixAdmin(admin.ModelAdmin):
  142. readonly_fields = ('prefix',)
  143. def has_delete_permission(self, request, obj=None):
  144. # Interdiction de supprimer le préfix s’il est assigné à une route
  145. if obj and obj.tunnel_set.exists():
  146. return False
  147. else:
  148. return True
  149. # pour embêcher de by-passer le check has_delete_permission, on désactive l’action delete
  150. def get_actions(self, request):
  151. actions = super().get_actions(request)
  152. if 'delete_selected' in actions:
  153. del actions['delete_selected']
  154. return actions
  155. class IPResourceAdmin(admin.ModelAdmin):
  156. list_display = ('__str__', 'available_display', 'last_use',)
  157. list_filter = (
  158. 'category',
  159. ResourceInUseFilter,
  160. ('prefixes', admin.RelatedOnlyFieldListFilter),
  161. RouteFilter,
  162. )
  163. fields = ('ip', 'reserved', 'notes')
  164. readonly_fields = ('ip', 'reserved',)
  165. search_fields = ('=ip',)
  166. def get_inline_instances(self, request, obj=None):
  167. if obj:
  168. if obj.category == 0:
  169. inlines = (ServiceAllocationInline,)
  170. elif obj.category == 1:
  171. inlines = (AntennaAllocationInline,)
  172. else:
  173. inlines = ()
  174. return [inline(self.model, self.admin_site) for inline in inlines]
  175. def get_queryset(self, request):
  176. qs = super().get_queryset(request)
  177. qs = qs.annotate(last_use=models.Case(
  178. models.When(category=0, then=models.Max('service_allocation__end')),
  179. models.When(category=1, then=models.Max('antenna_allocation__end')),
  180. ))
  181. return qs
  182. def available_display(self, obj):
  183. return not obj.reserved and not obj.in_use
  184. available_display.short_description = 'Disponible'
  185. available_display.boolean = True
  186. def last_use(self, obj):
  187. if obj.allocations.exists():
  188. return obj.allocations.last().end
  189. else:
  190. return None
  191. last_use.short_description = 'Dernière utilisation'
  192. last_use.admin_order_field = 'last_use'
  193. def get_actions(self, request):
  194. actions = super().get_actions(request)
  195. if 'delete_selected' in actions:
  196. del actions['delete_selected']
  197. return actions
  198. def has_add_permission(self, request, obj=None):
  199. return False
  200. def has_delete_permission(self, request, obj=None):
  201. return False
  202. class RouteAdmin(admin.ModelAdmin):
  203. list_display = ('name',)
  204. def get_readonly_fields(self, request, obj=None):
  205. if obj:
  206. return ('name',)
  207. else:
  208. return ()
  209. def get_actions(self, request):
  210. actions = super().get_actions(request)
  211. if 'delete_selected' in actions:
  212. del actions['delete_selected']
  213. return actions
  214. def has_delete_permission(self, request, obj=None):
  215. return False
  216. class TunnelAdmin(RouteAdmin):
  217. list_display = ('name', 'description', 'created', 'active')
  218. list_filter = (
  219. ActiveTunnelFilter,
  220. )
  221. def active(self, obj):
  222. return not obj.ended
  223. active.short_description = 'Actif'
  224. active.boolean = True
  225. class ServiceTypeAdmin(admin.ModelAdmin):
  226. fields = ('name',)
  227. readonly_fields = ('name',)
  228. def get_actions(self, request):
  229. actions = super().get_actions(request)
  230. if 'delete_selected' in actions:
  231. del actions['delete_selected']
  232. return actions
  233. def has_add_permission(self, request, obj=None):
  234. return False
  235. def has_delete_permission(self, request, obj=None):
  236. return False
  237. class AntennaAdmin(geo_admin.OSMGeoAdmin):
  238. list_display = ('id', 'label', 'ip_display')
  239. inlines = (AntennaAllocationInline,)
  240. list_filter = (
  241. AntennaPrefixFilter,
  242. )
  243. def ip_display(self, obj):
  244. allocations = obj.allocations.filter(active=True)
  245. return ', '.join(allocations.values_list('resource__ip', flat=True)) or '-'
  246. ip_display.short_description = 'IP'
  247. def get_actions(self, request):
  248. actions = super().get_actions(request)
  249. if 'delete_selected' in actions:
  250. del actions['delete_selected']
  251. return actions
  252. def has_delete_permission(self, request, obj=None):
  253. return False
  254. def get_urls(self):
  255. urls = super().get_urls()
  256. from djgeojson.views import GeoJSONLayerView
  257. my_urls = [
  258. url(r'^map/$', self.admin_site.admin_view(self.map_view, cacheable=True), name='antenna-map'),
  259. url(r'^map/data.json$', self.admin_site.admin_view(GeoJSONLayerView.as_view(model=Antenna, geometry_field='position')), name='antenna-map-data'),
  260. ]
  261. return my_urls + urls
  262. def map_view(self, request):
  263. return TemplateResponse(request, 'services/antenna_map.html', {
  264. 'json_url': reverse('admin:antenna-map-data'),
  265. })
  266. def map_data_view(self, request):
  267. geojson = serialize('geojson', Antenna.objects.all(), geometry_field='point', fields=('position',))
  268. return HttpResponse(geojson, content_type='application/json')
  269. admin.site.register(ServiceType, ServiceTypeAdmin)
  270. admin.site.register(Service, ServiceAdmin)
  271. admin.site.register(IPPrefix, IPPrefixAdmin)
  272. admin.site.register(IPResource, IPResourceAdmin)
  273. admin.site.register(Route, RouteAdmin)
  274. admin.site.register(Tunnel, TunnelAdmin)
  275. geo_admin.site.register(Antenna, AntennaAdmin)