from django.contrib.admin import SimpleListFilter from django.contrib.admin.utils import quote from django.shortcuts import redirect from django.urls import path, reverse from django.utils.translation import gettext_lazy as _ from wagtail.contrib.modeladmin.helpers import ( PermissionHelper, PagePermissionHelper, PageAdminURLHelper, AdminURLHelper, ButtonHelper) from wagtail.contrib.modeladmin.options import ModelAdmin from wagtail.contrib.modeladmin.views import IndexView, InstanceSpecificView from wagtail.admin import messages from wagtail import hooks from wagtail.models import Page from wagtail.contrib.forms.utils import get_forms_for_user from .models import SessionFormSubmission class FormIndexView(IndexView): page_title = _('Forms') class FormPermissionHelper(PagePermissionHelper): def user_can_list(self, user): return get_forms_for_user(user).exists() def user_can_create(self, user): return False def user_can_edit_obj(self, user, obj): return False def user_can_delete_obj(self, user, obj): return False def user_can_publish_obj(self, user, obj): return False def user_can_unpublish_obj(self, user, obj): return False def user_can_copy_obj(self, user, obj): return False def user_can_inspect_obj(self, user, obj): return False class FormURLHelper(PageAdminURLHelper): def _get_action_url_pattern(self, action): if action == 'index': return r'^stream_forms/$' return r'^stream_forms/%s/$' % action class FormAdmin(ModelAdmin): model = Page menu_label = _('Forms') menu_icon = 'form' list_display = ('title', 'unprocessed_submissions_link', 'all_submissions_link', 'edit_link') index_view_class = FormIndexView permission_helper_class = FormPermissionHelper url_helper_class = FormURLHelper def get_queryset(self, request): return get_forms_for_user(request.user) def all_submissions_link(self, obj, label=_('See all submissions'), url_suffix=''): return '%s' % ( reverse(SubmissionAdmin().url_helper.get_action_url_name('index')), obj.pk, url_suffix, label) all_submissions_link.short_description = '' all_submissions_link.allow_tags = True def unprocessed_submissions_link(self, obj): return self.all_submissions_link( obj, _('See unprocessed submissions'), '&status=%s' % SubmissionStatusFilter.unprocessed_status) unprocessed_submissions_link.short_description = '' unprocessed_submissions_link.allow_tags = True def edit_link(self, obj): return '%s' % ( reverse('wagtailadmin_pages:edit', args=(obj.pk,)), _('Edit this form page')) edit_link.short_description = '' edit_link.allow_tags = True class SubmissionStatusFilter(SimpleListFilter): title = _('status') parameter_name = 'status' unprocessed_status = ','.join((SessionFormSubmission.COMPLETE, SessionFormSubmission.REVIEWED)) def lookups(self, request, model_admin): yield (self.unprocessed_status, _('Complete or reviewed')) for status, verbose_status in SessionFormSubmission.STATUSES: if status != SessionFormSubmission.INCOMPLETE: yield status, verbose_status def queryset(self, request, queryset): status = self.value() if not status: return queryset if ',' in status: return queryset.filter(status__in=status.split(',')) return queryset.filter(status=status) class SubmissionPermissionHelper(PermissionHelper): def user_can_list(self, user): return get_forms_for_user(user).exists() def user_can_create(self, user): return False def user_can_edit_obj(self, user, obj): return False def user_can_inspect_obj(self, user, obj): return False def user_can_set_status_obj(self, user, obj): return user.can_set_status() class SubmissionURLHelper(AdminURLHelper): def _get_action_url_pattern(self, action): if action == 'index': return r'^%s/%s/$' % (self.opts.app_label, 'submissions') return r'^%s/%s/%s/$' % (self.opts.app_label, 'submissions', action) def _get_object_specific_action_url_pattern(self, action): return r'^%s/%s/%s/(?P[-\w]+)/$' % ( self.opts.app_label, 'submissions', action) class SubmissionButtonHelper(ButtonHelper): def set_status_button(self, pk, status, label, title, classnames_add=None, classnames_exclude=None): if classnames_add is None: classnames_add = [] if classnames_exclude is None: classnames_exclude = [] classnames = self.finalise_classname(classnames_add, classnames_exclude) url = self.url_helper.get_action_url('set_status', quote(pk)) url += '?status=' + status return { 'url': url, 'label': label, 'classname': classnames, 'title': title, } def reviewed_button(self, pk, classnames_add=None, classnames_exclude=None): if classnames_add is None: classnames_add = [] return self.set_status_button(pk, self.model.REVIEWED, _('mark as reviewed'), _('Mark this submission as reviewed'), classnames_add=classnames_add, classnames_exclude=classnames_exclude) def approve_button(self, pk, classnames_add=None, classnames_exclude=None): if classnames_add is None: classnames_add = [] if 'button-secondary' in classnames_add: classnames_add.remove('button-secondary') classnames_add = ['yes'] + classnames_add return self.set_status_button(pk, self.model.APPROVED, _('approve'), _('Approve this submission'), classnames_add=classnames_add, classnames_exclude=classnames_exclude) def reject_button(self, pk, classnames_add=None, classnames_exclude=None): if classnames_add is None: classnames_add = [] if 'button-secondary' in classnames_add: classnames_add.remove('button-secondary') classnames_add = ['no'] + classnames_add return self.set_status_button(pk, self.model.REJECTED, _('reject'), _('Reject this submission'), classnames_add=classnames_add, classnames_exclude=classnames_exclude) def get_buttons_for_obj(self, obj, exclude=None, classnames_add=None, classnames_exclude=None): buttons = super().get_buttons_for_obj( obj, exclude=exclude, classnames_add=classnames_add, classnames_exclude=classnames_exclude) pk = getattr(obj, self.opts.pk.attname) status_buttons = [] if obj.status != obj.REVIEWED: status_buttons.append(self.reviewed_button( pk, classnames_add=classnames_add, classnames_exclude=classnames_exclude)) if obj.status != obj.APPROVED: status_buttons.append(self.approve_button( pk, classnames_add=classnames_add, classnames_exclude=classnames_exclude)) if obj.status != obj.REJECTED: status_buttons.append(self.reject_button( pk, classnames_add=classnames_add, classnames_exclude=classnames_exclude)) return status_buttons + buttons class SetStatusView(InstanceSpecificView): def check_action_permitted(self, user): return self.permission_helper.user_can_set_status_obj(user, self.instance) def get(self, request, *args, **kwargs): status = request.GET.get('status') if status in dict(self.model.STATUSES): previous_status = self.instance.status self.instance.status = status self.instance.save() verbose_label = self.instance.get_status_display() if 'revert' in request.GET: messages.success(request, 'Reverted to the ā€œ%sā€ status.' % verbose_label) else: revert_url = (self.url_helper.get_action_url('set_status', self.instance_pk) + '?revert&status=' + previous_status) messages.success( request, 'Successfully changed the status to ā€œ%sā€.' % verbose_label, buttons=[messages.button(revert_url, _('Revert'))]) url = request.META.get('HTTP_REFERER') if url is None: url = (self.url_helper.get_action_url('index') + '?page_id=%s' % self.instance.page_id) return redirect(url) class SubmissionAdmin(ModelAdmin): model = SessionFormSubmission menu_icon = 'form' permission_helper_class = SubmissionPermissionHelper url_helper_class = SubmissionURLHelper button_helper_class = SubmissionButtonHelper set_status_view_class = SetStatusView list_display = ('status', 'user', 'submit_time', 'last_modification') list_filter = (SubmissionStatusFilter, 'submit_time', 'last_modification') search_fields = ('user__first_name', 'user__last_name') def register_with_wagtail(self): @hooks.register('register_permissions') def register_permissions(): return self.get_permissions_for_registration() @hooks.register('register_admin_urls') def register_admin_urls(): return self.get_admin_urls_for_registration() def get_queryset(self, request): qs = super().get_queryset(request) form_pages = get_forms_for_user(request.user) return (qs.filter(page__in=form_pages) .exclude(status=self.model.INCOMPLETE)) def get_form_page(self, request): form_pages = get_forms_for_user(request.user) try: return form_pages.get(pk=int(request.GET['page_id'])).specific except (KeyError, TypeError, ValueError, Page.DoesNotExist): pass # TODO: Find a cleaner way to display data from dynamic fields. def add_data_bridge(self, name, label): def data_bridge(obj): return obj.get_data().get(name) data_bridge.short_description = label setattr(self, name, data_bridge) def get_list_display(self, request): form_page = self.get_form_page(request) if form_page is None: return self.list_display fields = [] for name, label in form_page.get_data_fields(): fields.append(name) self.add_data_bridge(name, label) return fields def set_status_view(self, request, instance_pk): kwargs = {'model_admin': self, 'instance_pk': instance_pk} view_class = self.set_status_view_class return view_class.as_view(**kwargs)(request) def get_admin_urls_for_registration(self): urls = super().get_admin_urls_for_registration() urls += ( path( self.url_helper.get_action_url_pattern('set_status'), self.set_status_view, name=self.url_helper.get_action_url_name('set_status') ), ) return urls # @hooks.register('construct_main_menu') # def hide_old_forms_module(request, menu_items): # from wagtail.contrib.forms.wagtail_hooks import FormsMenuItem # for menu_item in menu_items: # if isinstance(menu_item, FormsMenuItem): # menu_items.remove(menu_item) # modeladmin_register(FormAdmin) # modeladmin_register(SubmissionAdmin)