views.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. from django.conf import settings
  2. from django.contrib import messages
  3. from django.contrib.admin.views.decorators import staff_member_required
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.core.urlresolvers import reverse
  6. from django.db import transaction, IntegrityError
  7. from django.db.models import ProtectedError
  8. from django.http import HttpResponseRedirect
  9. from django.shortcuts import get_object_or_404, redirect, render
  10. from django.template import TemplateSyntaxError
  11. from django.utils.decorators import method_decorator
  12. from django.utils.http import is_safe_url
  13. from django.views.generic import View
  14. from django_tables2 import RequestConfig
  15. from .error_handlers import handle_protectederror
  16. from .forms import ConfirmationForm
  17. from .paginator import EnhancedPaginator
  18. from extras.models import ExportTemplate
  19. class ObjectListView(View):
  20. queryset = None
  21. filter = None
  22. filter_form = None
  23. table = None
  24. edit_permissions = []
  25. template_name = None
  26. redirect_on_single_result = True
  27. def get(self, request, *args, **kwargs):
  28. object_ct = ContentType.objects.get_for_model(self.queryset.model)
  29. if self.filter:
  30. self.queryset = self.filter(request.GET, self.queryset).qs
  31. # Check for export template rendering
  32. if request.GET.get('export'):
  33. et = get_object_or_404(ExportTemplate, content_type=object_ct, name=request.GET.get('export'))
  34. try:
  35. response = et.to_response(context_dict={'queryset': self.queryset},
  36. filename='netbox_{}'.format(self.queryset.model._meta.verbose_name_plural))
  37. return response
  38. except TemplateSyntaxError:
  39. messages.error(request, "There was an error rendering the selected export template ({}).".format(et.name))
  40. # Attempt to redirect automatically if the query returns a single result
  41. if self.redirect_on_single_result and self.queryset.count() == 1:
  42. try:
  43. return HttpResponseRedirect(self.queryset[0].get_absolute_url())
  44. except AttributeError:
  45. pass
  46. # Provide a hook to tweak the queryset based on the request immediately prior to rendering the object list
  47. self.queryset = self.alter_queryset(request)
  48. # Construct the table based on the user's permissions
  49. table = self.table(self.queryset)
  50. if 'pk' in table.base_columns and any([request.user.has_perm(perm) for perm in self.edit_permissions]):
  51. table.base_columns['pk'].visible = True
  52. RequestConfig(request, paginate={'per_page': settings.PAGINATE_COUNT, 'klass': EnhancedPaginator})\
  53. .configure(table)
  54. export_templates = ExportTemplate.objects.filter(content_type=object_ct)
  55. return render(request, self.template_name, {
  56. 'table': table,
  57. 'filter_form': self.filter_form(request.GET, label_suffix='') if self.filter_form else None,
  58. 'export_templates': export_templates,
  59. })
  60. def alter_queryset(self, request):
  61. return self.queryset
  62. class ObjectAddView(View):
  63. model = None
  64. form_class = None
  65. template_name = 'utilities/obj_edit.html'
  66. cancel_url = None
  67. fields_initial = []
  68. def get(self, request):
  69. initial = {k: request.GET.get(k) for k in self.fields_initial}
  70. form = self.form_class(initial=initial)
  71. return render(request, self.template_name, {
  72. 'form': form,
  73. 'obj_type': self.model._meta.verbose_name,
  74. 'cancel_url': reverse(self.cancel_url),
  75. })
  76. def post(self, request):
  77. form = self.form_class(request.POST)
  78. if form.is_valid():
  79. obj = form.save()
  80. messages.success(request, 'Added new {} <a href="{}">{}</a>'.format(self.model._meta.verbose_name,
  81. obj.get_absolute_url(), obj))
  82. if '_addanother' in request.POST:
  83. return redirect(request.path)
  84. else:
  85. return redirect(obj.get_absolute_url())
  86. return render(request, self.template_name, {
  87. 'form': form,
  88. 'obj_type': self.model._meta.verbose_name,
  89. 'cancel_url': reverse(self.cancel_url),
  90. })
  91. class ObjectEditView(View):
  92. model = None
  93. form_class = None
  94. template_name = 'utilities/obj_edit.html'
  95. return_url = None
  96. def get_object(self, kwargs):
  97. # Look up object by slug if one has been provided. Otherwise, use PK.
  98. if 'slug' in kwargs:
  99. return get_object_or_404(self.model, slug=kwargs['slug'])
  100. else:
  101. return get_object_or_404(self.model, pk=kwargs['pk'])
  102. def get(self, request, *args, **kwargs):
  103. obj = self.get_object(kwargs)
  104. form = self.form_class(instance=obj)
  105. return render(request, self.template_name, {
  106. 'obj': obj,
  107. 'form': form,
  108. 'obj_type': self.model._meta.verbose_name,
  109. 'cancel_url': reverse(self.return_url) if self.return_url else obj.get_absolute_url(),
  110. })
  111. def post(self, request, *args, **kwargs):
  112. obj = self.get_object(kwargs)
  113. form = self.form_class(request.POST, instance=obj)
  114. if form.is_valid():
  115. obj = form.save()
  116. messages.success(request, 'Modified {} <a href="{}">{}</a>'.format(self.model._meta.verbose_name,
  117. obj.get_absolute_url(), obj))
  118. if '_addanother' in request.POST:
  119. return redirect(request.path)
  120. elif self.return_url:
  121. return redirect(self.return_url)
  122. else:
  123. return redirect(obj.get_absolute_url())
  124. return render(request, self.template_name, {
  125. 'obj': obj,
  126. 'form': form,
  127. 'obj_type': self.model._meta.verbose_name,
  128. 'cancel_url': reverse(self.return_url) if self.return_url else obj.get_absolute_url(),
  129. })
  130. class ObjectDeleteView(View):
  131. model = None
  132. template_name = 'utilities/obj_delete.html'
  133. redirect_url = None
  134. def get_object(self, kwargs):
  135. # Look up object by slug if one has been provided. Otherwise, use PK.
  136. if 'slug' in kwargs:
  137. return get_object_or_404(self.model, slug=kwargs['slug'])
  138. else:
  139. return get_object_or_404(self.model, pk=kwargs['pk'])
  140. def get(self, request, *args, **kwargs):
  141. obj = self.get_object(kwargs)
  142. form = ConfirmationForm()
  143. return render(request, self.template_name, {
  144. 'obj': obj,
  145. 'form': form,
  146. 'obj_type': self.model._meta.verbose_name,
  147. 'cancel_url': obj.get_absolute_url(),
  148. })
  149. def post(self, request, *args, **kwargs):
  150. obj = self.get_object(kwargs)
  151. form = ConfirmationForm(request.POST)
  152. if form.is_valid():
  153. try:
  154. obj.delete()
  155. messages.success(request, 'Deleted {} {}'.format(self.model._meta.verbose_name, obj))
  156. return redirect(self.redirect_url)
  157. except ProtectedError, e:
  158. handle_protectederror(obj, request, e)
  159. return redirect(obj.get_absolute_url())
  160. return render(request, self.template_name, {
  161. 'obj': obj,
  162. 'form': form,
  163. 'obj_type': self.model._meta.verbose_name,
  164. 'cancel_url': obj.get_absolute_url(),
  165. })
  166. class BulkImportView(View):
  167. form = None
  168. table = None
  169. template_name = None
  170. obj_list_url = None
  171. def get(self, request, *args, **kwargs):
  172. return render(request, self.template_name, {
  173. 'form': self.form(),
  174. 'obj_list_url': self.obj_list_url,
  175. })
  176. def post(self, request, *args, **kwargs):
  177. form = self.form(request.POST)
  178. if form.is_valid():
  179. new_objs = []
  180. try:
  181. with transaction.atomic():
  182. for obj in form.cleaned_data['csv']:
  183. self.save_obj(obj)
  184. new_objs.append(obj)
  185. obj_table = self.table(new_objs)
  186. messages.success(request, "Imported {} objects".format(len(new_objs)))
  187. return render(request, "import_success.html", {
  188. 'table': obj_table,
  189. })
  190. except IntegrityError as e:
  191. form.add_error('csv', "Record {}: {}".format(len(new_objs) + 1, e.__cause__))
  192. return render(request, self.template_name, {
  193. 'form': form,
  194. 'obj_list_url': self.obj_list_url,
  195. })
  196. def save_obj(self, obj):
  197. obj.save()
  198. class BulkEditView(View):
  199. cls = None
  200. form = None
  201. template_name = None
  202. default_redirect_url = None
  203. def get(self, request, *args, **kwargs):
  204. return redirect(self.default_redirect_url)
  205. def post(self, request, *args, **kwargs):
  206. posted_redirect_url = request.POST.get('redirect_url')
  207. if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
  208. redirect_url = posted_redirect_url
  209. else:
  210. redirect_url = reverse(self.default_redirect_url)
  211. if '_apply' in request.POST:
  212. form = self.form(request.POST)
  213. if form.is_valid():
  214. pk_list = [obj.pk for obj in form.cleaned_data['pk']]
  215. self.update_objects(pk_list, form)
  216. if not form.errors:
  217. return redirect(redirect_url)
  218. else:
  219. form = self.form(initial={'pk': request.POST.getlist('pk')})
  220. selected_objects = self.cls.objects.filter(pk__in=request.POST.getlist('pk'))
  221. if not selected_objects:
  222. messages.warning(request, "No {} were selected.".format(self.cls._meta.verbose_name_plural))
  223. return redirect(redirect_url)
  224. return render(request, self.template_name, {
  225. 'form': form,
  226. 'selected_objects': selected_objects,
  227. 'cancel_url': redirect_url,
  228. })
  229. def update_objects(self, obj_list, form):
  230. """
  231. This method provides the update logic (must be overridden by subclasses).
  232. """
  233. raise NotImplementedError()
  234. class BulkDeleteView(View):
  235. cls = None
  236. form = None
  237. template_name = 'utilities/confirm_bulk_delete.html'
  238. default_redirect_url = None
  239. @method_decorator(staff_member_required)
  240. def dispatch(self, *args, **kwargs):
  241. return super(BulkDeleteView, self).dispatch(*args, **kwargs)
  242. def get(self, request, *args, **kwargs):
  243. return redirect(self.default_redirect_url)
  244. def post(self, request, *args, **kwargs):
  245. posted_redirect_url = request.POST.get('redirect_url')
  246. if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
  247. redirect_url = posted_redirect_url
  248. else:
  249. redirect_url = reverse(self.default_redirect_url)
  250. if '_confirm' in request.POST:
  251. form = self.form(request.POST)
  252. if form.is_valid():
  253. # Delete objects
  254. objects_to_delete = self.cls.objects.filter(pk__in=[v.id for v in form.cleaned_data['pk']])
  255. try:
  256. deleted_count = objects_to_delete.count()
  257. objects_to_delete.delete()
  258. except ProtectedError, e:
  259. handle_protectederror(list(objects_to_delete), request, e)
  260. return redirect(redirect_url)
  261. messages.success(request, "Deleted {} {}".format(deleted_count, self.cls._meta.verbose_name_plural))
  262. return redirect(redirect_url)
  263. else:
  264. form = self.form(initial={'pk': request.POST.getlist('pk')})
  265. selected_objects = self.cls.objects.filter(pk__in=form.initial.get('pk'))
  266. if not selected_objects:
  267. messages.warning(request, "No {} were selected for deletion.".format(self.cls._meta.verbose_name_plural))
  268. return redirect(redirect_url)
  269. return render(request, self.template_name, {
  270. 'form': form,
  271. 'obj_type_plural': self.cls._meta.verbose_name_plural,
  272. 'selected_objects': selected_objects,
  273. 'cancel_url': redirect_url,
  274. })