views.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. from django import template
  2. from django.conf import settings
  3. from django.contrib import messages
  4. from django.contrib.auth.mixins import PermissionRequiredMixin
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.db.models import Count, Q
  7. from django.http import Http404, HttpResponseForbidden
  8. from django.shortcuts import get_object_or_404, redirect, render
  9. from django.utils.safestring import mark_safe
  10. from django.views.generic import View
  11. from django_tables2 import RequestConfig
  12. from utilities.forms import ConfirmationForm
  13. from utilities.paginator import EnhancedPaginator
  14. from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
  15. from . import filters, forms
  16. from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
  17. from .reports import get_report, get_reports
  18. from .scripts import get_scripts, run_script
  19. from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemTable
  20. #
  21. # Tags
  22. #
  23. class TagListView(PermissionRequiredMixin, ObjectListView):
  24. permission_required = 'extras.view_tag'
  25. queryset = Tag.objects.annotate(
  26. items=Count('extras_taggeditem_items', distinct=True)
  27. ).order_by(
  28. 'name'
  29. )
  30. filter = filters.TagFilter
  31. filter_form = forms.TagFilterForm
  32. table = TagTable
  33. template_name = 'extras/tag_list.html'
  34. class TagView(View):
  35. def get(self, request, slug):
  36. tag = get_object_or_404(Tag, slug=slug)
  37. tagged_items = TaggedItem.objects.filter(
  38. tag=tag
  39. ).prefetch_related(
  40. 'content_type', 'content_object'
  41. )
  42. # Generate a table of all items tagged with this Tag
  43. items_table = TaggedItemTable(tagged_items)
  44. paginate = {
  45. 'paginator_class': EnhancedPaginator,
  46. 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
  47. }
  48. RequestConfig(request, paginate).configure(items_table)
  49. return render(request, 'extras/tag.html', {
  50. 'tag': tag,
  51. 'items_count': tagged_items.count(),
  52. 'items_table': items_table,
  53. })
  54. class TagEditView(PermissionRequiredMixin, ObjectEditView):
  55. permission_required = 'extras.change_tag'
  56. model = Tag
  57. model_form = forms.TagForm
  58. default_return_url = 'extras:tag_list'
  59. template_name = 'extras/tag_edit.html'
  60. class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  61. permission_required = 'extras.delete_tag'
  62. model = Tag
  63. default_return_url = 'extras:tag_list'
  64. class TagBulkEditView(PermissionRequiredMixin, BulkEditView):
  65. permission_required = 'extras.change_tag'
  66. queryset = Tag.objects.annotate(
  67. items=Count('extras_taggeditem_items', distinct=True)
  68. ).order_by(
  69. 'name'
  70. )
  71. # filter = filters.ProviderFilter
  72. table = TagTable
  73. form = forms.TagBulkEditForm
  74. default_return_url = 'circuits:provider_list'
  75. class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  76. permission_required = 'extras.delete_tag'
  77. queryset = Tag.objects.annotate(
  78. items=Count('extras_taggeditem_items')
  79. ).order_by(
  80. 'name'
  81. )
  82. table = TagTable
  83. default_return_url = 'extras:tag_list'
  84. #
  85. # Config contexts
  86. #
  87. class ConfigContextListView(PermissionRequiredMixin, ObjectListView):
  88. permission_required = 'extras.view_configcontext'
  89. queryset = ConfigContext.objects.all()
  90. filter = filters.ConfigContextFilter
  91. filter_form = forms.ConfigContextFilterForm
  92. table = ConfigContextTable
  93. template_name = 'extras/configcontext_list.html'
  94. class ConfigContextView(PermissionRequiredMixin, View):
  95. permission_required = 'extras.view_configcontext'
  96. def get(self, request, pk):
  97. configcontext = get_object_or_404(ConfigContext, pk=pk)
  98. return render(request, 'extras/configcontext.html', {
  99. 'configcontext': configcontext,
  100. })
  101. class ConfigContextCreateView(PermissionRequiredMixin, ObjectEditView):
  102. permission_required = 'extras.add_configcontext'
  103. model = ConfigContext
  104. model_form = forms.ConfigContextForm
  105. default_return_url = 'extras:configcontext_list'
  106. template_name = 'extras/configcontext_edit.html'
  107. class ConfigContextEditView(ConfigContextCreateView):
  108. permission_required = 'extras.change_configcontext'
  109. class ConfigContextBulkEditView(PermissionRequiredMixin, BulkEditView):
  110. permission_required = 'extras.change_configcontext'
  111. queryset = ConfigContext.objects.all()
  112. filter = filters.ConfigContextFilter
  113. table = ConfigContextTable
  114. form = forms.ConfigContextBulkEditForm
  115. default_return_url = 'extras:configcontext_list'
  116. class ConfigContextDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  117. permission_required = 'extras.delete_configcontext'
  118. model = ConfigContext
  119. default_return_url = 'extras:configcontext_list'
  120. class ConfigContextBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  121. permission_required = 'extras.delete_configcontext'
  122. queryset = ConfigContext.objects.all()
  123. table = ConfigContextTable
  124. default_return_url = 'extras:configcontext_list'
  125. class ObjectConfigContextView(View):
  126. object_class = None
  127. base_template = None
  128. def get(self, request, pk):
  129. obj = get_object_or_404(self.object_class, pk=pk)
  130. source_contexts = ConfigContext.objects.get_for_object(obj)
  131. model_name = self.object_class._meta.model_name
  132. return render(request, 'extras/object_configcontext.html', {
  133. model_name: obj,
  134. 'obj': obj,
  135. 'rendered_context': obj.get_config_context(),
  136. 'source_contexts': source_contexts,
  137. 'base_template': self.base_template,
  138. 'active_tab': 'config-context',
  139. })
  140. #
  141. # Change logging
  142. #
  143. class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
  144. permission_required = 'extras.view_objectchange'
  145. queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
  146. filter = filters.ObjectChangeFilter
  147. filter_form = forms.ObjectChangeFilterForm
  148. table = ObjectChangeTable
  149. template_name = 'extras/objectchange_list.html'
  150. class ObjectChangeView(PermissionRequiredMixin, View):
  151. permission_required = 'extras.view_objectchange'
  152. def get(self, request, pk):
  153. objectchange = get_object_or_404(ObjectChange, pk=pk)
  154. related_changes = ObjectChange.objects.filter(request_id=objectchange.request_id).exclude(pk=objectchange.pk)
  155. related_changes_table = ObjectChangeTable(
  156. data=related_changes[:50],
  157. orderable=False
  158. )
  159. return render(request, 'extras/objectchange.html', {
  160. 'objectchange': objectchange,
  161. 'related_changes_table': related_changes_table,
  162. 'related_changes_count': related_changes.count()
  163. })
  164. class ObjectChangeLogView(View):
  165. """
  166. Present a history of changes made to a particular object.
  167. """
  168. def get(self, request, model, **kwargs):
  169. # Get object my model and kwargs (e.g. slug='foo')
  170. obj = get_object_or_404(model, **kwargs)
  171. # Gather all changes for this object (and its related objects)
  172. content_type = ContentType.objects.get_for_model(model)
  173. objectchanges = ObjectChange.objects.prefetch_related(
  174. 'user', 'changed_object_type'
  175. ).filter(
  176. Q(changed_object_type=content_type, changed_object_id=obj.pk) |
  177. Q(related_object_type=content_type, related_object_id=obj.pk)
  178. )
  179. objectchanges_table = ObjectChangeTable(
  180. data=objectchanges,
  181. orderable=False
  182. )
  183. # Apply the request context
  184. paginate = {
  185. 'paginator_class': EnhancedPaginator,
  186. 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
  187. }
  188. RequestConfig(request, paginate).configure(objectchanges_table)
  189. # Check whether a header template exists for this model
  190. base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
  191. try:
  192. template.loader.get_template(base_template)
  193. object_var = model._meta.model_name
  194. except template.TemplateDoesNotExist:
  195. base_template = '_base.html'
  196. object_var = 'obj'
  197. return render(request, 'extras/object_changelog.html', {
  198. object_var: obj,
  199. 'table': objectchanges_table,
  200. 'base_template': base_template,
  201. 'active_tab': 'changelog',
  202. })
  203. #
  204. # Image attachments
  205. #
  206. class ImageAttachmentEditView(PermissionRequiredMixin, ObjectEditView):
  207. permission_required = 'extras.change_imageattachment'
  208. model = ImageAttachment
  209. model_form = forms.ImageAttachmentForm
  210. def alter_obj(self, imageattachment, request, args, kwargs):
  211. if not imageattachment.pk:
  212. # Assign the parent object based on URL kwargs
  213. model = kwargs.get('model')
  214. imageattachment.parent = get_object_or_404(model, pk=kwargs['object_id'])
  215. return imageattachment
  216. def get_return_url(self, request, imageattachment):
  217. return imageattachment.parent.get_absolute_url()
  218. class ImageAttachmentDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  219. permission_required = 'extras.delete_imageattachment'
  220. model = ImageAttachment
  221. def get_return_url(self, request, imageattachment):
  222. return imageattachment.parent.get_absolute_url()
  223. #
  224. # Reports
  225. #
  226. class ReportListView(PermissionRequiredMixin, View):
  227. """
  228. Retrieve all of the available reports from disk and the recorded ReportResult (if any) for each.
  229. """
  230. permission_required = 'extras.view_reportresult'
  231. def get(self, request):
  232. reports = get_reports()
  233. results = {r.report: r for r in ReportResult.objects.all()}
  234. ret = []
  235. for module, report_list in reports:
  236. module_reports = []
  237. for report in report_list:
  238. report.result = results.get(report.full_name, None)
  239. module_reports.append(report)
  240. ret.append((module, module_reports))
  241. return render(request, 'extras/report_list.html', {
  242. 'reports': ret,
  243. })
  244. class ReportView(PermissionRequiredMixin, View):
  245. """
  246. Display a single Report and its associated ReportResult (if any).
  247. """
  248. permission_required = 'extras.view_reportresult'
  249. def get(self, request, name):
  250. # Retrieve the Report by "<module>.<report>"
  251. module_name, report_name = name.split('.')
  252. report = get_report(module_name, report_name)
  253. if report is None:
  254. raise Http404
  255. # Attach the ReportResult (if any)
  256. report.result = ReportResult.objects.filter(report=report.full_name).first()
  257. return render(request, 'extras/report.html', {
  258. 'report': report,
  259. 'run_form': ConfirmationForm(),
  260. })
  261. class ReportRunView(PermissionRequiredMixin, View):
  262. """
  263. Run a Report and record a new ReportResult.
  264. """
  265. permission_required = 'extras.add_reportresult'
  266. def post(self, request, name):
  267. # Retrieve the Report by "<module>.<report>"
  268. module_name, report_name = name.split('.')
  269. report = get_report(module_name, report_name)
  270. if report is None:
  271. raise Http404
  272. form = ConfirmationForm(request.POST)
  273. if form.is_valid():
  274. # Run the Report. A new ReportResult is created.
  275. report.run()
  276. result = 'failed' if report.failed else 'passed'
  277. msg = "Ran report {} ({})".format(report.full_name, result)
  278. messages.success(request, mark_safe(msg))
  279. return redirect('extras:report', name=report.full_name)
  280. #
  281. # Scripts
  282. #
  283. class ScriptListView(PermissionRequiredMixin, View):
  284. permission_required = 'extras.view_script'
  285. def get(self, request):
  286. return render(request, 'extras/script_list.html', {
  287. 'scripts': get_scripts(),
  288. })
  289. class ScriptView(PermissionRequiredMixin, View):
  290. permission_required = 'extras.view_script'
  291. def _get_script(self, module, name):
  292. scripts = get_scripts()
  293. try:
  294. return scripts[module][name]()
  295. except KeyError:
  296. raise Http404
  297. def get(self, request, module, name):
  298. script = self._get_script(module, name)
  299. form = script.as_form(initial=request.GET)
  300. return render(request, 'extras/script.html', {
  301. 'module': module,
  302. 'script': script,
  303. 'form': form,
  304. })
  305. def post(self, request, module, name):
  306. # Permissions check
  307. if not request.user.has_perm('extras.run_script'):
  308. return HttpResponseForbidden()
  309. script = self._get_script(module, name)
  310. form = script.as_form(request.POST, request.FILES)
  311. output = None
  312. execution_time = None
  313. if form.is_valid():
  314. commit = form.cleaned_data.pop('_commit')
  315. output, execution_time = run_script(script, form.cleaned_data, request, commit)
  316. return render(request, 'extras/script.html', {
  317. 'module': module,
  318. 'script': script,
  319. 'form': form,
  320. 'output': output,
  321. 'execution_time': execution_time,
  322. })