Skip to content

Commit

Permalink
Merge pull request #17 from johncronan/dev
Browse files Browse the repository at this point in the history
fixes; 'submission records' inline in admin; reviewpanel 0.7.5
  • Loading branch information
johncronan authored Apr 12, 2022
2 parents eaa0695 + daa9adc commit fedc95f
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 59 deletions.
75 changes: 38 additions & 37 deletions formative/admin/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,6 @@
from ..utils import send_email


@admin.action(description='Move to different page')
def move_blocks_action(modeladmin, request, queryset):
if '_move' in request.POST:
page, n = request.POST['page'], 0
blocks, cur_page = queryset[0].form.blocks, queryset[0].page
last_rank = blocks.filter(page=cur_page).aggregate(r=Max('_rank'))['r']
for block in queryset.order_by('_rank'):
block = FormBlock.objects.get(pk=block.pk) # clear side effects;
block._rank = last_rank
block.save() # it's now at the end of its page;
block.page, block._rank = page, None # and then move to the new one,
block.save() # so that the others on the old page are left sorted
n, last_rank = n + 1, last_rank - 1

msg = f'Moved {n} blocks to page {page}'
modeladmin.message_user(request, msg, messages.SUCCESS)

return HttpResponseRedirect(request.get_full_path())

template_name = 'admin/formative/move_page.html'
request.current_app = modeladmin.admin_site.name
min_page = max(block.min_allowed_page() for block in queryset)
last_page = queryset[0].form.blocks.aggregate(p=Max('page'))['p']
max_page = min(block.max_allowed_page(last_page) for block in queryset)
new = max_page == last_page
context = {
**modeladmin.admin_site.each_context(request),
'opts': modeladmin.model._meta,
'media': modeladmin.media,
'blocks': queryset,
'movable': queryset[0].form.status == Form.Status.DRAFT,
'form': MoveBlocksAdminForm(max_page, min_page=min_page, new_page=new),
'title': 'Move Blocks'
}
return TemplateResponse(request, template_name, context)


EMAILS_PER_SECOND = 10

@admin.action(description='Send an email to applicants')
Expand Down Expand Up @@ -228,6 +191,44 @@ def response_change(self, request, obj):
return super().response_change(request, obj)


class FormBlockActionsMixin:
@admin.action(description='Move to different page')
def move_blocks_action(self, request, queryset):
if '_move' in request.POST:
page, n = request.POST['page'], 0
blocks, cur_page = queryset[0].form.blocks, queryset[0].page
last_rank_q = blocks.filter(page=cur_page).aggregate(r=Max('_rank'))
last_rank = last_rank_q['r']
for block in cur_page and queryset.order_by('_rank') or []:
block = FormBlock.objects.get(pk=block.pk) # clear side effects;
block._rank = last_rank
block.save() # it's now at the end of its page; and
block.page, block._rank = page, None # then move to the new one,
block.save() # so that the others on the old page are left sorted
n, last_rank = n + 1, last_rank - 1

msg = f'Moved {n} blocks to page {page}'
if n: self.message_user(request, msg, messages.SUCCESS)

return HttpResponseRedirect(request.get_full_path())

template_name = 'admin/formative/move_page.html'
request.current_app = self.admin_site.name
min_page = max(block.min_allowed_page() for block in queryset)
last_page = queryset[0].form.blocks.aggregate(p=Max('page'))['p']
max_page = min(block.max_allowed_page(last_page) for block in queryset)
new = max_page == last_page
context = {
**self.admin_site.each_context(request),
'opts': self.model._meta, 'media': self.media,
'blocks': queryset, 'title': 'Move Blocks',
'movable': queryset[0].form.status == Form.Status.DRAFT,
'form': MoveBlocksAdminForm(max_page,
min_page=min_page, new_page=new),
}
return TemplateResponse(request, template_name, context)


class SubmissionActionsMixin:
def change_view(self, request, object_id, **kwargs):
action, kwargs = None, {}
Expand Down
65 changes: 58 additions & 7 deletions formative/admin/formative.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@
PolymorphicChildModelAdmin,
PolymorphicChildModelFilter)
import types
from functools import partial
from urllib.parse import unquote, parse_qsl

from ..forms import ProgramAdminForm, FormAdminForm, StockBlockAdminForm, \
CustomBlockAdminForm, CollectionBlockAdminForm, SubmissionAdminForm, \
SubmissionItemAdminForm
from ..models import Program, Form, FormLabel, FormBlock, FormDependency, \
CustomBlock, CollectionBlock
CustomBlock, CollectionBlock, SubmissionRecord
from ..filetype import FileType
from ..plugins import get_matching_plugin
from ..signals import register_program_settings, register_form_settings, \
register_user_actions, form_published_changed
from ..utils import submission_link
from .actions import move_blocks_action, send_email_action, UserActionsMixin, \
FormActionsMixin, SubmissionActionsMixin
from .actions import send_email_action, UserActionsMixin, FormActionsMixin, \
FormBlockActionsMixin, SubmissionActionsMixin


class FormativeAdminSite(admin.AdminSite):
Expand Down Expand Up @@ -364,20 +365,21 @@ def lookups(self, request, model_admin):
for p in qs.values_list('page', flat=True) ]

def queryset(self, request, queryset):
if not self.value().isdigit(): return queryset.none()
return queryset.filter(page=self.value())


@admin.register(FormBlock, site=site)
class FormBlockAdmin(FormBlockBase, PolymorphicParentModelAdmin,
DynamicArrayMixin):
class FormBlockAdmin(FormBlockActionsMixin, FormBlockBase,
PolymorphicParentModelAdmin, DynamicArrayMixin):
child_models = (FormBlock, CustomBlock, CollectionBlock)
list_display = ('_rank', 'name', 'block_type', 'dependence', 'labels_link')
list_display_links = ('name',)
list_editable = ('_rank',)
list_filter = (PageListFilter,)
form = StockBlockAdminForm
inlines = [FormDependencyInline]
actions = [move_blocks_action]
actions = ['move_blocks_action']
sortable_by = ()
polymorphic_list = True

Expand Down Expand Up @@ -460,10 +462,15 @@ def get_queryset(self, request):
return qs

def get_changelist_form(self, request, **kwargs):
page = request.GET.get('page')
if not page.isdigit(): page = None

class HiddenWithHandleInput(forms.HiddenInput):
template_name = 'admin/formative/widgets/hidden_with_handle.html'

kwargs['widgets'] = {'_rank': HiddenWithHandleInput}
if 'form_id' in request.resolver_match.kwargs and page and int(page):
kwargs['widgets'] = {'_rank': HiddenWithHandleInput}
else: kwargs['widgets'] = {'_rank': forms.HiddenInput}
return super().get_changelist_form(request, **kwargs)

def change_view(self, *args, **kwargs):
Expand All @@ -489,6 +496,17 @@ def get_preserved_filters(self, request):
args['form_id'] = match.kwargs['form_id']
return urlencode(args)
return ''

def has_delete_permission(self, request, obj=None):
match = request.resolver_match
if 'form_id' not in match.kwargs: return False
try: form = Form.objects.get(id=match.kwargs['form_id'])
except Form.DoesNotExist: return False
if form.status != Form.Status.DRAFT: return False
page = request.GET.get('page')
if not page.isdigit(): page = None
if page and not int(page): return False
return True


class FormBlockChildAdmin(FormBlockBase, PolymorphicChildModelAdmin):
Expand Down Expand Up @@ -602,11 +620,33 @@ def queryset(self, request, queryset):
if self.value() == 'no': return queryset.filter(_submitted=None)


class SubmissionRecordFormSet(forms.BaseModelFormSet):
@classmethod
def get_default_prefix(cls): return 'formative-submissionrecord'

class SubmissionRecordInline(admin.TabularInline):
model = SubmissionRecord
formset = SubmissionRecordFormSet
exclude = ('program', 'form', 'submission')
readonly_fields = ('type', 'recorded', 'text', 'number', 'deleted')

def has_add_permission(self, request, obj=None): return False

def get_formset(self, request, obj=None, **kwargs):
return forms.modelformset_factory(self.model, **{
'form': self.form, 'formset': self.formset, 'fields': (),
'formfield_callback': partial(self.formfield_for_dbfield,
request=request),
'extra': 0, 'max_num': 0, 'can_delete': False, 'can_order': False
})


class SubmissionAdmin(SubmissionActionsMixin, admin.ModelAdmin):
list_display = ('_email', '_created', '_modified', '_submitted')
list_filter = ('_email', SubmittedListFilter)
readonly_fields = ('_submitted', 'items_index',)
form = SubmissionAdminForm
inlines = [SubmissionRecordInline]
actions = [send_email_action]

def delete_queryset(self, request, queryset):
Expand All @@ -616,6 +656,17 @@ def delete_queryset(self, request, queryset):
flat=True)).delete()
super().delete_queryset(request, queryset)

def get_formsets_with_inlines(self, request, obj=None):
for inl in self.get_inline_instances(request, obj):
if not isinstance(inl, SubmissionRecordInline) or obj is not None:
yield inl.get_formset(request, obj), inl

def get_formset_kwargs(self, request, obj, inline, prefix):
args = super().get_formset_kwargs(request, obj, inline, prefix)
for n in ('instance', 'save_as_new'): args.pop(n, None)
args['queryset'] = SubmissionRecord.objects.filter(submission=obj.pk)
return args

@admin.display(description='items')
def items_index(self, obj):
app, name = self.model._meta.app_label, self.model._meta.model_name
Expand Down
2 changes: 2 additions & 0 deletions formative/models/formative.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,12 +553,14 @@ def enabled_blocks(self, value, page=None):
return query.values_list('id', flat=True)

def min_allowed_page(self):
if not self.page: return 0
min_page = 1

if self.dependence: min_page = self.dependence.page + 1
return min_page

def max_allowed_page(self, last_page=None):
if not self.page: return 0
if last_page is None:
last_page = self.form.blocks.aggregate(p=Max('page'))['p'] or 1
max_page = last_page
Expand Down
28 changes: 14 additions & 14 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ markdown-link-attr-modifier = "^0.2.0"
django-better-admin-arrayfield = "^1.4.2"
django-admin-inline-paginator = "*"
whitenoise = "^6.0.0"
reviewpanel = { version = "^0.7.1", optional = true }
reviewpanel = { version = "^0.7.5", optional = true }

[tool.poetry.dev-dependencies]
pytest = "*"
Expand Down

0 comments on commit fedc95f

Please sign in to comment.