2
0

wagtail_hooks.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. from django.contrib.admin import SimpleListFilter
  2. from django.contrib.admin.utils import quote
  3. from django.shortcuts import redirect
  4. from django.urls import path, reverse
  5. from django.utils.translation import gettext_lazy as _
  6. from wagtail.contrib.modeladmin.helpers import (
  7. PermissionHelper, PagePermissionHelper, PageAdminURLHelper, AdminURLHelper,
  8. ButtonHelper)
  9. from wagtail.contrib.modeladmin.options import ModelAdmin
  10. from wagtail.contrib.modeladmin.views import IndexView, InstanceSpecificView
  11. from wagtail.admin import messages
  12. from wagtail import hooks
  13. from wagtail.models import Page
  14. from wagtail.contrib.forms.utils import get_forms_for_user
  15. from .models import SessionFormSubmission
  16. class FormIndexView(IndexView):
  17. page_title = _('Forms')
  18. class FormPermissionHelper(PagePermissionHelper):
  19. def user_can_list(self, user):
  20. return get_forms_for_user(user).exists()
  21. def user_can_create(self, user):
  22. return False
  23. def user_can_edit_obj(self, user, obj):
  24. return False
  25. def user_can_delete_obj(self, user, obj):
  26. return False
  27. def user_can_publish_obj(self, user, obj):
  28. return False
  29. def user_can_unpublish_obj(self, user, obj):
  30. return False
  31. def user_can_copy_obj(self, user, obj):
  32. return False
  33. def user_can_inspect_obj(self, user, obj):
  34. return False
  35. class FormURLHelper(PageAdminURLHelper):
  36. def _get_action_url_pattern(self, action):
  37. if action == 'index':
  38. return r'^stream_forms/$'
  39. return r'^stream_forms/%s/$' % action
  40. class FormAdmin(ModelAdmin):
  41. model = Page
  42. menu_label = _('Forms')
  43. menu_icon = 'form'
  44. list_display = ('title', 'unprocessed_submissions_link',
  45. 'all_submissions_link', 'edit_link')
  46. index_view_class = FormIndexView
  47. permission_helper_class = FormPermissionHelper
  48. url_helper_class = FormURLHelper
  49. def get_queryset(self, request):
  50. return get_forms_for_user(request.user)
  51. def all_submissions_link(self, obj, label=_('See all submissions'),
  52. url_suffix=''):
  53. return '<a href="%s?page_id=%s%s">%s</a>' % (
  54. reverse(SubmissionAdmin().url_helper.get_action_url_name('index')),
  55. obj.pk, url_suffix, label)
  56. all_submissions_link.short_description = ''
  57. all_submissions_link.allow_tags = True
  58. def unprocessed_submissions_link(self, obj):
  59. return self.all_submissions_link(
  60. obj, _('See unprocessed submissions'),
  61. '&status=%s' % SubmissionStatusFilter.unprocessed_status)
  62. unprocessed_submissions_link.short_description = ''
  63. unprocessed_submissions_link.allow_tags = True
  64. def edit_link(self, obj):
  65. return '<a href="%s">%s</a>' % (
  66. reverse('wagtailadmin_pages:edit', args=(obj.pk,)),
  67. _('Edit this form page'))
  68. edit_link.short_description = ''
  69. edit_link.allow_tags = True
  70. class SubmissionStatusFilter(SimpleListFilter):
  71. title = _('status')
  72. parameter_name = 'status'
  73. unprocessed_status = ','.join((SessionFormSubmission.COMPLETE,
  74. SessionFormSubmission.REVIEWED))
  75. def lookups(self, request, model_admin):
  76. yield (self.unprocessed_status, _('Complete or reviewed'))
  77. for status, verbose_status in SessionFormSubmission.STATUSES:
  78. if status != SessionFormSubmission.INCOMPLETE:
  79. yield status, verbose_status
  80. def queryset(self, request, queryset):
  81. status = self.value()
  82. if not status:
  83. return queryset
  84. if ',' in status:
  85. return queryset.filter(status__in=status.split(','))
  86. return queryset.filter(status=status)
  87. class SubmissionPermissionHelper(PermissionHelper):
  88. def user_can_list(self, user):
  89. return get_forms_for_user(user).exists()
  90. def user_can_create(self, user):
  91. return False
  92. def user_can_edit_obj(self, user, obj):
  93. return False
  94. def user_can_inspect_obj(self, user, obj):
  95. return False
  96. def user_can_set_status_obj(self, user, obj):
  97. return user.can_set_status()
  98. class SubmissionURLHelper(AdminURLHelper):
  99. def _get_action_url_pattern(self, action):
  100. if action == 'index':
  101. return r'^%s/%s/$' % (self.opts.app_label, 'submissions')
  102. return r'^%s/%s/%s/$' % (self.opts.app_label, 'submissions', action)
  103. def _get_object_specific_action_url_pattern(self, action):
  104. return r'^%s/%s/%s/(?P<instance_pk>[-\w]+)/$' % (
  105. self.opts.app_label, 'submissions', action)
  106. class SubmissionButtonHelper(ButtonHelper):
  107. def set_status_button(self, pk, status, label, title, classnames_add=None,
  108. classnames_exclude=None):
  109. if classnames_add is None:
  110. classnames_add = []
  111. if classnames_exclude is None:
  112. classnames_exclude = []
  113. classnames = self.finalise_classname(classnames_add,
  114. classnames_exclude)
  115. url = self.url_helper.get_action_url('set_status', quote(pk))
  116. url += '?status=' + status
  117. return {
  118. 'url': url,
  119. 'label': label,
  120. 'classname': classnames,
  121. 'title': title,
  122. }
  123. def reviewed_button(self, pk, classnames_add=None,
  124. classnames_exclude=None):
  125. if classnames_add is None:
  126. classnames_add = []
  127. return self.set_status_button(pk, self.model.REVIEWED,
  128. _('mark as reviewed'),
  129. _('Mark this submission as reviewed'),
  130. classnames_add=classnames_add,
  131. classnames_exclude=classnames_exclude)
  132. def approve_button(self, pk, classnames_add=None,
  133. classnames_exclude=None):
  134. if classnames_add is None:
  135. classnames_add = []
  136. if 'button-secondary' in classnames_add:
  137. classnames_add.remove('button-secondary')
  138. classnames_add = ['yes'] + classnames_add
  139. return self.set_status_button(pk, self.model.APPROVED, _('approve'),
  140. _('Approve this submission'),
  141. classnames_add=classnames_add,
  142. classnames_exclude=classnames_exclude)
  143. def reject_button(self, pk, classnames_add=None,
  144. classnames_exclude=None):
  145. if classnames_add is None:
  146. classnames_add = []
  147. if 'button-secondary' in classnames_add:
  148. classnames_add.remove('button-secondary')
  149. classnames_add = ['no'] + classnames_add
  150. return self.set_status_button(pk, self.model.REJECTED, _('reject'),
  151. _('Reject this submission'),
  152. classnames_add=classnames_add,
  153. classnames_exclude=classnames_exclude)
  154. def get_buttons_for_obj(self, obj, exclude=None, classnames_add=None,
  155. classnames_exclude=None):
  156. buttons = super().get_buttons_for_obj(
  157. obj, exclude=exclude, classnames_add=classnames_add,
  158. classnames_exclude=classnames_exclude)
  159. pk = getattr(obj, self.opts.pk.attname)
  160. status_buttons = []
  161. if obj.status != obj.REVIEWED:
  162. status_buttons.append(self.reviewed_button(
  163. pk, classnames_add=classnames_add,
  164. classnames_exclude=classnames_exclude))
  165. if obj.status != obj.APPROVED:
  166. status_buttons.append(self.approve_button(
  167. pk, classnames_add=classnames_add,
  168. classnames_exclude=classnames_exclude))
  169. if obj.status != obj.REJECTED:
  170. status_buttons.append(self.reject_button(
  171. pk, classnames_add=classnames_add,
  172. classnames_exclude=classnames_exclude))
  173. return status_buttons + buttons
  174. class SetStatusView(InstanceSpecificView):
  175. def check_action_permitted(self, user):
  176. return self.permission_helper.user_can_set_status_obj(user,
  177. self.instance)
  178. def get(self, request, *args, **kwargs):
  179. status = request.GET.get('status')
  180. if status in dict(self.model.STATUSES):
  181. previous_status = self.instance.status
  182. self.instance.status = status
  183. self.instance.save()
  184. verbose_label = self.instance.get_status_display()
  185. if 'revert' in request.GET:
  186. messages.success(request, 'Reverted to the “%s” status.'
  187. % verbose_label)
  188. else:
  189. revert_url = (self.url_helper.get_action_url('set_status',
  190. self.instance_pk)
  191. + '?revert&status=' + previous_status)
  192. messages.success(
  193. request,
  194. 'Successfully changed the status to “%s”.' % verbose_label,
  195. buttons=[messages.button(revert_url, _('Revert'))])
  196. url = request.META.get('HTTP_REFERER')
  197. if url is None:
  198. url = (self.url_helper.get_action_url('index')
  199. + '?page_id=%s' % self.instance.page_id)
  200. return redirect(url)
  201. class SubmissionAdmin(ModelAdmin):
  202. model = SessionFormSubmission
  203. menu_icon = 'form'
  204. permission_helper_class = SubmissionPermissionHelper
  205. url_helper_class = SubmissionURLHelper
  206. button_helper_class = SubmissionButtonHelper
  207. set_status_view_class = SetStatusView
  208. list_display = ('status', 'user', 'submit_time', 'last_modification')
  209. list_filter = (SubmissionStatusFilter, 'submit_time', 'last_modification')
  210. search_fields = ('user__first_name', 'user__last_name')
  211. def register_with_wagtail(self):
  212. @hooks.register('register_permissions')
  213. def register_permissions():
  214. return self.get_permissions_for_registration()
  215. @hooks.register('register_admin_urls')
  216. def register_admin_urls():
  217. return self.get_admin_urls_for_registration()
  218. def get_queryset(self, request):
  219. qs = super().get_queryset(request)
  220. form_pages = get_forms_for_user(request.user)
  221. return (qs.filter(page__in=form_pages)
  222. .exclude(status=self.model.INCOMPLETE))
  223. def get_form_page(self, request):
  224. form_pages = get_forms_for_user(request.user)
  225. try:
  226. return form_pages.get(pk=int(request.GET['page_id'])).specific
  227. except (KeyError, TypeError, ValueError, Page.DoesNotExist):
  228. pass
  229. # TODO: Find a cleaner way to display data from dynamic fields.
  230. def add_data_bridge(self, name, label):
  231. def data_bridge(obj):
  232. return obj.get_data().get(name)
  233. data_bridge.short_description = label
  234. setattr(self, name, data_bridge)
  235. def get_list_display(self, request):
  236. form_page = self.get_form_page(request)
  237. if form_page is None:
  238. return self.list_display
  239. fields = []
  240. for name, label in form_page.get_data_fields():
  241. fields.append(name)
  242. self.add_data_bridge(name, label)
  243. return fields
  244. def set_status_view(self, request, instance_pk):
  245. kwargs = {'model_admin': self, 'instance_pk': instance_pk}
  246. view_class = self.set_status_view_class
  247. return view_class.as_view(**kwargs)(request)
  248. def get_admin_urls_for_registration(self):
  249. urls = super().get_admin_urls_for_registration()
  250. urls += (
  251. path(
  252. self.url_helper.get_action_url_pattern('set_status'),
  253. self.set_status_view,
  254. name=self.url_helper.get_action_url_name('set_status')
  255. ),
  256. )
  257. return urls
  258. # @hooks.register('construct_main_menu')
  259. # def hide_old_forms_module(request, menu_items):
  260. # from wagtail.contrib.forms.wagtail_hooks import FormsMenuItem
  261. # for menu_item in menu_items:
  262. # if isinstance(menu_item, FormsMenuItem):
  263. # menu_items.remove(menu_item)
  264. # modeladmin_register(FormAdmin)
  265. # modeladmin_register(SubmissionAdmin)