views.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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
  8. from django.shortcuts import get_object_or_404, redirect, render
  9. from django.utils.safestring import mark_safe
  10. from django.utils.decorators import method_decorator
  11. from django.views.decorators.cache import cache_page
  12. from django.views.generic import View
  13. from django_tables2 import RequestConfig
  14. from utilities.forms import ConfirmationForm
  15. from utilities.paginator import EnhancedPaginator
  16. from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
  17. from . import filters
  18. from .forms import (
  19. ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm,
  20. TagFilterForm, TagForm,
  21. )
  22. from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
  23. from .reports import get_report, get_reports
  24. from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemTable
  25. #
  26. # Tags
  27. #
  28. class TagListView(ObjectListView):
  29. queryset = Tag.objects.annotate(
  30. items=Count('extras_taggeditem_items')
  31. ).order_by(
  32. 'name'
  33. )
  34. filter = filters.TagFilter
  35. filter_form = TagFilterForm
  36. table = TagTable
  37. template_name = 'extras/tag_list.html'
  38. class TagView(View):
  39. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  40. def get(self, request, slug):
  41. tag = get_object_or_404(Tag, slug=slug)
  42. tagged_items = TaggedItem.objects.filter(
  43. tag=tag
  44. ).select_related(
  45. 'content_type'
  46. ).prefetch_related(
  47. 'content_object'
  48. )
  49. # Generate a table of all items tagged with this Tag
  50. items_table = TaggedItemTable(tagged_items)
  51. paginate = {
  52. 'paginator_class': EnhancedPaginator,
  53. 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
  54. }
  55. RequestConfig(request, paginate).configure(items_table)
  56. return render(request, 'extras/tag.html', {
  57. 'tag': tag,
  58. 'items_count': tagged_items.count(),
  59. 'items_table': items_table,
  60. })
  61. class TagEditView(PermissionRequiredMixin, ObjectEditView):
  62. permission_required = 'extras.change_tag'
  63. model = Tag
  64. model_form = TagForm
  65. default_return_url = 'extras:tag_list'
  66. template_name = 'extras/tag_edit.html'
  67. class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  68. permission_required = 'extras.delete_tag'
  69. model = Tag
  70. default_return_url = 'extras:tag_list'
  71. class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  72. permission_required = 'extras.delete_tag'
  73. queryset = Tag.objects.annotate(
  74. items=Count('extras_taggeditem_items')
  75. ).order_by(
  76. 'name'
  77. )
  78. table = TagTable
  79. default_return_url = 'extras:tag_list'
  80. #
  81. # Config contexts
  82. #
  83. class ConfigContextListView(PermissionRequiredMixin, ObjectListView):
  84. permission_required = 'extras.view_configcontext'
  85. queryset = ConfigContext.objects.all()
  86. filter = filters.ConfigContextFilter
  87. filter_form = ConfigContextFilterForm
  88. table = ConfigContextTable
  89. template_name = 'extras/configcontext_list.html'
  90. class ConfigContextView(PermissionRequiredMixin, View):
  91. permission_required = 'extras.view_configcontext'
  92. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  93. def get(self, request, pk):
  94. configcontext = get_object_or_404(ConfigContext, pk=pk)
  95. return render(request, 'extras/configcontext.html', {
  96. 'configcontext': configcontext,
  97. })
  98. class ConfigContextCreateView(PermissionRequiredMixin, ObjectEditView):
  99. permission_required = 'extras.add_configcontext'
  100. model = ConfigContext
  101. model_form = ConfigContextForm
  102. default_return_url = 'extras:configcontext_list'
  103. template_name = 'extras/configcontext_edit.html'
  104. class ConfigContextEditView(ConfigContextCreateView):
  105. permission_required = 'extras.change_configcontext'
  106. class ConfigContextBulkEditView(PermissionRequiredMixin, BulkEditView):
  107. permission_required = 'extras.change_configcontext'
  108. queryset = ConfigContext.objects.all()
  109. filter = filters.ConfigContextFilter
  110. table = ConfigContextTable
  111. form = ConfigContextBulkEditForm
  112. default_return_url = 'extras:configcontext_list'
  113. class ConfigContextDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  114. permission_required = 'extras.delete_configcontext'
  115. model = ConfigContext
  116. default_return_url = 'extras:configcontext_list'
  117. class ConfigContextBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  118. permission_required = 'extras.delete_cconfigcontext'
  119. queryset = ConfigContext.objects.all()
  120. table = ConfigContextTable
  121. default_return_url = 'extras:configcontext_list'
  122. class ObjectConfigContextView(View):
  123. object_class = None
  124. base_template = None
  125. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  126. def get(self, request, pk):
  127. obj = get_object_or_404(self.object_class, pk=pk)
  128. source_contexts = ConfigContext.objects.get_for_object(obj)
  129. model_name = self.object_class._meta.model_name
  130. return render(request, 'extras/object_configcontext.html', {
  131. model_name: obj,
  132. 'obj': obj,
  133. 'rendered_context': obj.get_config_context(),
  134. 'source_contexts': source_contexts,
  135. 'base_template': self.base_template,
  136. 'active_tab': 'config-context',
  137. })
  138. #
  139. # Change logging
  140. #
  141. class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
  142. permission_required = 'extras.view_objectchange'
  143. queryset = ObjectChange.objects.select_related('user', 'changed_object_type')
  144. filter = filters.ObjectChangeFilter
  145. filter_form = ObjectChangeFilterForm
  146. table = ObjectChangeTable
  147. template_name = 'extras/objectchange_list.html'
  148. class ObjectChangeView(PermissionRequiredMixin, View):
  149. permission_required = 'extras.view_objectchange'
  150. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  151. def get(self, request, pk):
  152. objectchange = get_object_or_404(ObjectChange, pk=pk)
  153. related_changes = ObjectChange.objects.filter(request_id=objectchange.request_id).exclude(pk=objectchange.pk)
  154. related_changes_table = ObjectChangeTable(
  155. data=related_changes[:50],
  156. orderable=False
  157. )
  158. return render(request, 'extras/objectchange.html', {
  159. 'objectchange': objectchange,
  160. 'related_changes_table': related_changes_table,
  161. 'related_changes_count': related_changes.count()
  162. })
  163. class ObjectChangeLogView(View):
  164. """
  165. Present a history of changes made to a particular object.
  166. """
  167. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  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.select_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. # Check whether a header template exists for this model
  184. base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
  185. try:
  186. template.loader.get_template(base_template)
  187. object_var = model._meta.model_name
  188. except template.TemplateDoesNotExist:
  189. base_template = '_base.html'
  190. object_var = 'obj'
  191. return render(request, 'extras/object_changelog.html', {
  192. object_var: obj,
  193. 'objectchanges_table': objectchanges_table,
  194. 'base_template': base_template,
  195. 'active_tab': 'changelog',
  196. })
  197. #
  198. # Image attachments
  199. #
  200. class ImageAttachmentEditView(PermissionRequiredMixin, ObjectEditView):
  201. permission_required = 'extras.change_imageattachment'
  202. model = ImageAttachment
  203. model_form = ImageAttachmentForm
  204. def alter_obj(self, imageattachment, request, args, kwargs):
  205. if not imageattachment.pk:
  206. # Assign the parent object based on URL kwargs
  207. model = kwargs.get('model')
  208. imageattachment.parent = get_object_or_404(model, pk=kwargs['object_id'])
  209. return imageattachment
  210. def get_return_url(self, request, imageattachment):
  211. return imageattachment.parent.get_absolute_url()
  212. class ImageAttachmentDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  213. permission_required = 'extras.delete_imageattachment'
  214. model = ImageAttachment
  215. def get_return_url(self, request, imageattachment):
  216. return imageattachment.parent.get_absolute_url()
  217. #
  218. # Reports
  219. #
  220. class ReportListView(PermissionRequiredMixin, View):
  221. """
  222. Retrieve all of the available reports from disk and the recorded ReportResult (if any) for each.
  223. """
  224. permission_required = 'extras.view_reportresult'
  225. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  226. def get(self, request):
  227. reports = get_reports()
  228. results = {r.report: r for r in ReportResult.objects.all()}
  229. ret = []
  230. for module, report_list in reports:
  231. module_reports = []
  232. for report in report_list:
  233. report.result = results.get(report.full_name, None)
  234. module_reports.append(report)
  235. ret.append((module, module_reports))
  236. return render(request, 'extras/report_list.html', {
  237. 'reports': ret,
  238. })
  239. class ReportView(PermissionRequiredMixin, View):
  240. """
  241. Display a single Report and its associated ReportResult (if any).
  242. """
  243. permission_required = 'extras.view_reportresult'
  244. @method_decorator(cache_page(settings.CACHE_TIMEOUT))
  245. def get(self, request, name):
  246. # Retrieve the Report by "<module>.<report>"
  247. module_name, report_name = name.split('.')
  248. report = get_report(module_name, report_name)
  249. if report is None:
  250. raise Http404
  251. # Attach the ReportResult (if any)
  252. report.result = ReportResult.objects.filter(report=report.full_name).first()
  253. return render(request, 'extras/report.html', {
  254. 'report': report,
  255. 'run_form': ConfirmationForm(),
  256. })
  257. class ReportRunView(PermissionRequiredMixin, View):
  258. """
  259. Run a Report and record a new ReportResult.
  260. """
  261. permission_required = 'extras.add_reportresult'
  262. def post(self, request, name):
  263. # Retrieve the Report by "<module>.<report>"
  264. module_name, report_name = name.split('.')
  265. report = get_report(module_name, report_name)
  266. if report is None:
  267. raise Http404
  268. form = ConfirmationForm(request.POST)
  269. if form.is_valid():
  270. # Run the Report. A new ReportResult is created.
  271. report.run()
  272. result = 'failed' if report.failed else 'passed'
  273. msg = "Ran report {} ({})".format(report.full_name, result)
  274. messages.success(request, mark_safe(msg))
  275. return redirect('extras:report', name=report.full_name)