From c48e0f1954bae717129dceacd274918c1cf9019c Mon Sep 17 00:00:00 2001 From: ifaint Date: Tue, 27 Oct 2020 15:53:47 +0800 Subject: [PATCH] Removed delete AJAX requests. Added edit_participation_role/tag permissions. --- course/constants.py | 8 + course/enrollment.py | 156 ++++---------- ...14_added_edit_prole_and_ptag_permission.py | 49 +++++ course/models.py | 2 + course/templates/course/course-base.html | 17 +- .../course/gradebook-participant-list.html | 2 +- .../course/participation-role-list.html | 6 +- .../course/participation-role-table.html | 57 +---- .../course/participation-tag-list.html | 4 - .../course/participation-tag-table.html | 56 +---- relate/urls.py | 14 -- tests/test_enrollment.py | 204 +++++++----------- 12 files changed, 197 insertions(+), 378 deletions(-) create mode 100644 course/migrations/0114_added_edit_prole_and_ptag_permission.py diff --git a/course/constants.py b/course/constants.py index a69f819da..e58e9a7c3 100644 --- a/course/constants.py +++ b/course/constants.py @@ -134,6 +134,8 @@ class participation_permission: # noqa query_participation = "query_participation" edit_participation = "edit_participation" preapprove_participation = "preapprove_participation" + edit_participation_role = "edit_participation_role" + edit_participation_tag = "edit_participation_tag" manage_instant_flow_requests = "manage_instant_flow_requests" @@ -249,6 +251,12 @@ class participation_permission: # noqa pgettext_lazy("Participation permission", "Edit participation")), (participation_permission.preapprove_participation, pgettext_lazy("Participation permission", "Preapprove participation")), + (participation_permission.edit_participation_role, + pgettext_lazy( + "Participation permission", "Edit participation role")), + (participation_permission.edit_participation_tag, + pgettext_lazy( + "Participation permission", "Edit participation tag")), (participation_permission.manage_instant_flow_requests, pgettext_lazy("Participation permission", diff --git a/course/enrollment.py b/course/enrollment.py index 90f06ea5b..e0e08e387 100644 --- a/course/enrollment.py +++ b/course/enrollment.py @@ -1124,10 +1124,12 @@ def __init__(self, add_new, *args, **kwargs): if add_new: self.helper.add_input( - Submit("submit", _("Add"))) + Submit("submit", _("Add"), css_class="btn-success")) else: self.helper.add_input( - Submit("submit", _("Update"))) + Submit("submit", _("Update"), css_class="btn-success")) + self.helper.add_input( + Submit("delete", _("Delete"), css_class="btn-danger")) class Meta: model = ParticipationTag @@ -1143,18 +1145,13 @@ def view_participation_tag_list(pctx): return render_course_page(pctx, "course/participation-tag-list.html", { "participation_tags": participation_tags, - - # Wrappers used by JavaScript template (tmpl) so as not to - # conflict with Django template's tag wrapper - "JQ_OPEN": "{%", - "JQ_CLOSE": "%}", }) @course_view def edit_participation_tag(pctx, ptag_id): # type: (CoursePageContext, int) -> http.HttpResponse - if not pctx.has_permission(pperm.edit_participation): + if not pctx.has_permission(pperm.edit_participation_tag): raise PermissionDenied() request = pctx.request @@ -1176,13 +1173,23 @@ def edit_participation_tag(pctx, ptag_id): form = EditParticipationTagForm(add_new, request.POST, instance=ptag) try: if form.is_valid(): - # Ref: https://stackoverflow.com/q/21458387/3437454 - with transaction.atomic(): - form.save() - if add_new: - msg = _("New participation tag saved.") + if "submit" in request.POST or "update" in request.POST: + # Ref: https://stackoverflow.com/q/21458387/3437454 + with transaction.atomic(): + form.save() + + if "submit" in request.POST: + assert add_new + msg = _("New participation tag saved.") + else: + msg = _("Changes saved.") + elif "delete" in request.POST: + ptag.delete() + msg = (_("successfully deleted participation tag '%(tag)s'.") + % {"tag": ptag.name}) else: - msg = _("Changes saved.") + raise SuspiciousOperation(_("invalid operation")) + messages.add_message(request, messages.SUCCESS, msg) return redirect( "relate-view_participation_tags", pctx.course.identifier) @@ -1199,48 +1206,6 @@ def edit_participation_tag(pctx, ptag_id): "form": form, }) - -@course_view -def delete_participation_tag(pctx, ptag_id): - # type: (CoursePageContext, int) -> http.HttpResponse - - if not pctx.has_permission(pperm.edit_participation): - raise PermissionDenied() - - request = pctx.request - - if not request.is_ajax() or request.method != "POST": - raise PermissionDenied(_("only AJAX POST is allowed")) - - num_ptag_id = int(ptag_id) - - ptag = get_object_or_404(ParticipationTag, id=num_ptag_id) - - if ptag.course.id != pctx.course.id: - raise SuspiciousOperation( - "may not delete participation tag in different course") - - if "delete" in request.POST: - try: - ptag.delete() - except Exception as e: - return http.JsonResponse( - {"error": _( - "Error when deleting participation tag '%(tag)s'." - " %(error_type)s: %(error)s.") % { - "tag": ptag.name, - "error_type": type(e).__name__, - "error": str(e)}}, - status=400) - else: - return http.JsonResponse( - {"message": _("successfully deleted participation tag '%(tag)s'.") - % {"tag": ptag.name}, - "message_level": messages.DEFAULT_TAGS[messages.SUCCESS]}) - - else: - raise SuspiciousOperation(_("invalid operation")) - # }}} @@ -1253,10 +1218,12 @@ def __init__(self, add_new, *args, **kwargs): if add_new: self.helper.add_input( - Submit("submit", _("Add"))) + Submit("submit", _("Add"), css_class="btn-success")) else: self.helper.add_input( - Submit("submit", _("Update"))) + Submit("submit", _("Update"), css_class="btn-success")) + self.helper.add_input( + Submit("delete", _("Delete"), css_class="btn-danger")) class Meta: model = ParticipationRole @@ -1272,18 +1239,13 @@ def view_participation_role_list(pctx): return render_course_page(pctx, "course/participation-role-list.html", { "participation_roles": participation_roles, - - # Wrappers used by JavaScript template (tmpl) so as not to - # conflict with Django template's tag wrapper - "JQ_OPEN": "{%", - "JQ_CLOSE": "%}", }) @course_view def edit_participation_role(pctx, prole_id): # type: (CoursePageContext, int) -> http.HttpResponse - if not pctx.has_permission(pperm.edit_participation): + if not pctx.has_permission(pperm.edit_participation_role): raise PermissionDenied() request = pctx.request @@ -1305,14 +1267,24 @@ def edit_participation_role(pctx, prole_id): form = EditParticipationRoleForm(add_new, request.POST, instance=prole) try: if form.is_valid(): - # Ref: https://stackoverflow.com/q/21458387/3437454 - with transaction.atomic(): - form.save() - - if add_new: - msg = _("New participation role saved.") + if "submit" in request.POST or "update" in request.POST: + # Ref: https://stackoverflow.com/q/21458387/3437454 + with transaction.atomic(): + form.save() + + if "submit" in request.POST: + assert add_new + msg = _("New participation role saved.") + else: + msg = _("Changes saved.") + elif "delete" in request.POST: + prole.delete() + msg = ( + _("successfully deleted participation role '%(role)s'.") + % {"role": prole.identifier}) else: - msg = _("Changes saved.") + raise SuspiciousOperation(_("invalid operation")) + messages.add_message(request, messages.SUCCESS, msg) return redirect( "relate-view_participation_roles", pctx.course.identifier) @@ -1329,48 +1301,6 @@ def edit_participation_role(pctx, prole_id): "form": form, }) - -@course_view -def delete_participation_role(pctx, prole_id): - # type: (CoursePageContext, int) -> http.HttpResponse - - if not pctx.has_permission(pperm.edit_participation): - raise PermissionDenied() - - request = pctx.request - - if not request.is_ajax() or request.method != "POST": - raise PermissionDenied(_("only AJAX POST is allowed")) - - num_prole_id = int(prole_id) - - prole = get_object_or_404(ParticipationRole, id=num_prole_id) - - if prole.course.id != pctx.course.id: - raise SuspiciousOperation( - "may not delete participation role in different course") - - if "delete" in request.POST: - try: - prole.delete() - except Exception as e: - return http.JsonResponse( - {"error": _( - "Error when deleting participation role '%(role)s'." - " %(error_type)s: %(error)s.") % { - "role": prole.identifier, - "error_type": type(e).__name__, - "error": str(e)}}, - status=400) - else: - return http.JsonResponse( - {"message": _("successfully deleted participation role '%(role)s'.") - % {"role": prole.identifier}, - "message_level": messages.DEFAULT_TAGS[messages.SUCCESS]}) - - else: - raise SuspiciousOperation(_("invalid operation")) - # }}} diff --git a/course/migrations/0114_added_edit_prole_and_ptag_permission.py b/course/migrations/0114_added_edit_prole_and_ptag_permission.py new file mode 100644 index 000000000..e48378048 --- /dev/null +++ b/course/migrations/0114_added_edit_prole_and_ptag_permission.py @@ -0,0 +1,49 @@ +# Generated by Django 3.0.8 on 2020-10-26 08:48 + +from __future__ import unicode_literals + +from django.db import migrations, models + + +def add_edit_prole_and_ptag_permission(apps, schema_editor): + from course.constants import participation_permission as pperm + + ParticipationRolePermission = apps.get_model("course", "ParticipationRolePermission") # noqa + + roles_pks = ( + ParticipationRolePermission.objects.filter( + permission=pperm.preapprove_participation) + .values_list("role", flat=True) + ) + + if roles_pks.count(): + for pk in roles_pks: + ParticipationRolePermission.objects.get_or_create( + role_id=pk, + permission=pperm.edit_participation_role + ) + ParticipationRolePermission.objects.get_or_create( + role_id=pk, + permission=pperm.edit_participation_tag + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0113_merge_20190919_1408'), + ] + + operations = [ + migrations.AlterField( + model_name='participationpermission', + name='permission', + field=models.CharField(choices=[('edit_course', 'Edit course'), ('use_admin_interface', 'Use admin interface'), ('manage_authentication_tokens', 'Manage authentication tokens'), ('impersonate_role', 'Impersonate role'), ('set_fake_time', 'Set fake time'), ('set_pretend_facility', 'Pretend to be in facility'), ('edit_course_permissions', 'Edit course permissions'), ('view_hidden_course_page', 'View hidden course page'), ('view_calendar', 'View calendar'), ('send_instant_message', 'Send instant message'), ('access_files_for', 'Access files for'), ('included_in_grade_statistics', 'Included in grade statistics'), ('skip_during_manual_grading', 'Skip during manual grading'), ('edit_exam', 'Edit exam'), ('issue_exam_ticket', 'Issue exam ticket'), ('batch_issue_exam_ticket', 'Batch issue exam ticket'), ('view_participant_masked_profile', "View participants' masked profile only"), ('view_flow_sessions_from_role', 'View flow sessions from role'), ('view_gradebook', 'View gradebook'), ('edit_grading_opportunity', 'Edit grading opportunity'), ('assign_grade', 'Assign grade'), ('view_grader_stats', 'View grader stats'), ('batch_import_grade', 'Batch-import grades'), ('batch_export_grade', 'Batch-export grades'), ('batch_download_submission', 'Batch-download submissions'), ('impose_flow_session_deadline', 'Impose flow session deadline'), ('batch_impose_flow_session_deadline', 'Batch-impose flow session deadline'), ('end_flow_session', 'End flow session'), ('batch_end_flow_session', 'Batch-end flow sessions'), ('regrade_flow_session', 'Regrade flow session'), ('batch_regrade_flow_session', 'Batch-regrade flow sessions'), ('recalculate_flow_session_grade', 'Recalculate flow session grade'), ('batch_recalculate_flow_session_grade', 'Batch-recalculate flow sesssion grades'), ('reopen_flow_session', 'Reopen flow session'), ('grant_exception', 'Grant exception'), ('view_analytics', 'View analytics'), ('preview_content', 'Preview content'), ('update_content', 'Update content'), ('use_git_endpoint', 'Use direct git endpoint'), ('use_markup_sandbox', 'Use markup sandbox'), ('use_page_sandbox', 'Use page sandbox'), ('test_flow', 'Test flow'), ('edit_events', 'Edit events'), ('query_participation', 'Query participation'), ('edit_participation', 'Edit participation'), ('preapprove_participation', 'Preapprove participation'), ('edit_participation_role', 'Edit participation role'), ('edit_participation_tag', 'Edit participation tag'), ('manage_instant_flow_requests', 'Manage instant flow requests')], db_index=True, max_length=200, verbose_name='Permission'), + ), + migrations.AlterField( + model_name='participationrolepermission', + name='permission', + field=models.CharField(choices=[('edit_course', 'Edit course'), ('use_admin_interface', 'Use admin interface'), ('manage_authentication_tokens', 'Manage authentication tokens'), ('impersonate_role', 'Impersonate role'), ('set_fake_time', 'Set fake time'), ('set_pretend_facility', 'Pretend to be in facility'), ('edit_course_permissions', 'Edit course permissions'), ('view_hidden_course_page', 'View hidden course page'), ('view_calendar', 'View calendar'), ('send_instant_message', 'Send instant message'), ('access_files_for', 'Access files for'), ('included_in_grade_statistics', 'Included in grade statistics'), ('skip_during_manual_grading', 'Skip during manual grading'), ('edit_exam', 'Edit exam'), ('issue_exam_ticket', 'Issue exam ticket'), ('batch_issue_exam_ticket', 'Batch issue exam ticket'), ('view_participant_masked_profile', "View participants' masked profile only"), ('view_flow_sessions_from_role', 'View flow sessions from role'), ('view_gradebook', 'View gradebook'), ('edit_grading_opportunity', 'Edit grading opportunity'), ('assign_grade', 'Assign grade'), ('view_grader_stats', 'View grader stats'), ('batch_import_grade', 'Batch-import grades'), ('batch_export_grade', 'Batch-export grades'), ('batch_download_submission', 'Batch-download submissions'), ('impose_flow_session_deadline', 'Impose flow session deadline'), ('batch_impose_flow_session_deadline', 'Batch-impose flow session deadline'), ('end_flow_session', 'End flow session'), ('batch_end_flow_session', 'Batch-end flow sessions'), ('regrade_flow_session', 'Regrade flow session'), ('batch_regrade_flow_session', 'Batch-regrade flow sessions'), ('recalculate_flow_session_grade', 'Recalculate flow session grade'), ('batch_recalculate_flow_session_grade', 'Batch-recalculate flow sesssion grades'), ('reopen_flow_session', 'Reopen flow session'), ('grant_exception', 'Grant exception'), ('view_analytics', 'View analytics'), ('preview_content', 'Preview content'), ('update_content', 'Update content'), ('use_git_endpoint', 'Use direct git endpoint'), ('use_markup_sandbox', 'Use markup sandbox'), ('use_page_sandbox', 'Use page sandbox'), ('test_flow', 'Test flow'), ('edit_events', 'Edit events'), ('query_participation', 'Query participation'), ('edit_participation', 'Edit participation'), ('preapprove_participation', 'Preapprove participation'), ('edit_participation_role', 'Edit participation role'), ('edit_participation_tag', 'Edit participation tag'), ('manage_instant_flow_requests', 'Manage instant flow requests')], db_index=True, max_length=200, verbose_name='Permission'), + ), + migrations.RunPython(add_edit_prole_and_ptag_permission), + ] diff --git a/course/models.py b/course/models.py index 2926fe810..42c06d6fb 100644 --- a/course/models.py +++ b/course/models.py @@ -736,6 +736,8 @@ def add_instructor_permissions(role): rpm(role=role, permission=pp.edit_events).save() rpm(role=role, permission=pp.manage_instant_flow_requests).save() rpm(role=role, permission=pp.preapprove_participation).save() + rpm(role=role, permission=pp.edit_participation_tag).save() + rpm(role=role, permission=pp.edit_participation_role).save() add_teaching_assistant_permissions(role) diff --git a/course/templates/course/course-base.html b/course/templates/course/course-base.html index 8e15836b8..d9db3f0d4 100644 --- a/course/templates/course/course-base.html +++ b/course/templates/course/course-base.html @@ -52,13 +52,16 @@