Skip to content

Commit

Permalink
Merge pull request apluslms#350 from jrp6/disable-non-submittable
Browse files Browse the repository at this point in the history
Show warning overlay when exercise cannot be submitted
  • Loading branch information
raphendyr authored Jul 16, 2018
2 parents cec7bc7 + f4cadeb commit 1c7bcee
Show file tree
Hide file tree
Showing 12 changed files with 409 additions and 199 deletions.
22 changes: 22 additions & 0 deletions assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,25 @@ td.submissions-dropdown {
table.results-table .category-row {
font-weight: bold;
}

/* overlays */
.overlay-parent {
position: relative;
padding-bottom: 15px; /* Same as .form-group margin-bottom */
}

.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 15;
background-color: #000b;
}

.overlay .panel {
margin: auto;
margin-top: 4rem;
width: 80%;
}
7 changes: 4 additions & 3 deletions exercise/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ def grader_detail(self, request, *args, **kwargs):
)

# find out if student can submit new exercise and if ok create submission template
ok, errors, students = exercise.is_submission_allowed(student)
if not ok:
status, errors, students = exercise.check_submission_allowed(student)
if status != exercise.SUBMIT_STATUS.ALLOWED:
return Response({'success': False, 'errors': errors})
submission = Submission.objects.create(exercise=exercise)
submission.submitters = students
Expand Down Expand Up @@ -178,7 +178,8 @@ def create(self, request, exercise_id, version):

print(exercice_to_submit)

if exercice_to_submit.is_submission_allowed([submitter]):
status, _, _ = exercise_to_submit.check_submission_allowed([submitter])
if status == exercise_to_submit.SUBMIT_STATUS.ALLOWED:
print("Submission is available.")

#serializer = SubmissionSerializer(data=request.data)
Expand Down
73 changes: 53 additions & 20 deletions exercise/exercise_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,16 @@ class BaseExercise(LearningObject):
('CLOSED_AFTER', 4, "Submissions are not anymore accepted"),
('ARCHIVED', 5, "Course is archived and so are exercises"),
])

SUBMIT_STATUS = Enum([
('ALLOWED', 1, ''),
('CANNOT_ENROLL', 2, 'You cannot enroll in the course.'),
('NOT_ENROLLED', 3, 'You must enroll at course home.'),
('INVALID_GROUP', 4, 'The selected group is not acceptable.'),
('AMOUNT_EXCEEDED', 5, 'You have used the allowed amount of submissions.'),
('INVALID', 999, 'You cannot submit for an unspecified reason.'),
])

allow_assistant_viewing = models.BooleanField(default=True)
allow_assistant_grading = models.BooleanField(default=False)
min_group_size = models.PositiveIntegerField(default=1)
Expand Down Expand Up @@ -454,7 +464,7 @@ def no_submissions_left(self, students):
return False
return True

def is_submission_allowed(self, profile, request=None):
def check_submission_allowed(self, profile, request=None):
"""
Checks whether the submission to this exercise is allowed for the given
user and generates a list of warnings.
Expand All @@ -480,10 +490,18 @@ def _check_submission_allowed(self, profile, request=None):
LearningObject.STATUS.ENROLLMENT_EXTERNAL,
):
if not self.course_instance.is_enrollable(profile.user):
return False, [_('You cannot enroll in the course.')], students
return (self.SUBMIT_STATUS.CANNOT_ENROLL,
[_('You cannot enroll in the course.')],
students)
elif not enrollment:
# TODO Provide button to enroll, should there be option to auto-enroll
return self.course_instance.is_course_staff(profile.user), [_('You must enroll at course home to submit exercises.')], students
if self.course_instance.is_course_staff(profile.user):
return (self.SUBMIT_STATUS.ALLOWED,
[_('Staff can submit exercises without enrolling.')],
students)
return (self.SUBMIT_STATUS.NOT_ENROLLED,
[_('You must enroll at course home to submit exercises.')],
students)

# Support group id from post or currently selected group.
group = None
Expand All @@ -494,8 +512,7 @@ def _check_submission_allowed(self, profile, request=None):
if gid > 0:
group = profile.groups.filter(
course_instance=self.course_instance,
id=gid
).first()
id=gid).first()
except ValueError:
pass
elif enrollment and enrollment.selected_group:
Expand All @@ -506,19 +523,23 @@ def _check_submission_allowed(self, profile, request=None):
if len(submissions) > 0:
s = submissions[0]
if self._detect_group_changes(profile, group, s):
msg = str(_("Group can only change between different exercises."))
msg = _("Group can only change between different exercises.")
warning = _('You have previously submitted this '
'exercise {with_group}. {msg}')
if s.submitters.count() == 1:
warnings.append(_("You have previously submitted to this exercise alone.") + " " + msg)
warning = warning.format(with_group=_('alone'), msg=msg)
else:
warnings.append(_("You have previously submitted to this exercise with {collaborators}.").format(
collaborators=StudentGroup.format_collaborator_names(s.submitters.all(), profile)
) + " " + msg)
return False, warnings, students
collaborators = StudentGroup.format_collaborator_names(
s.submitters.all(), profile)
with_group = _('with {}').format(collaborators)
warning = warning.format(with_group=with_group, msg=msg)
warnings.append(warning)
return self.SUBMIT_STATUS.INVALID_GROUP, warnings, students

elif self._detect_submissions(profile, group):
warnings.append(_('{collaborators} already submitted to this exercise in a different group.').format(
collaborators=group.collaborator_names(profile)
))
return False, warnings, students
collaborators=group.collaborator_names(profile)))
return self.SUBMIT_STATUS.INVALID_GROUP, warnings, students

# Get submitters.
if group:
Expand All @@ -530,23 +551,35 @@ def _check_submission_allowed(self, profile, request=None):
size = "{:d}".format(self.min_group_size)
else:
size = "{:d}-{:d}".format(self.min_group_size, self.max_group_size)
warnings.append(_("This exercise must be submitted in groups of {size} students.").format(
size=size
))
warnings.append(
_("This exercise must be submitted in groups of {size} students.")
.format(size=size))

access_ok,access_warnings = self.one_has_access(students)

if not self.one_has_submissions(students):
if self.course_module.late_submissions_allowed:
access_warnings.append(_('You have used the allowed amount of submissions for this exercise. You may still submit unofficially to receive feedback.'))
else:
warnings.append(_('You have used the allowed amount of submissions for this exercise.'))
return (self.SUBMIT_STATUS.ALLOWED,
warnings + access_warnings,
students)
warnings.append(_('You have used the allowed amount of submissions for this exercise.'))
return (self.SUBMIT_STATUS.AMOUNT_EXCEEDED,
warnings + access_warnings,
students)

ok = (
(access_ok and len(warnings) == 0) or
all(self.course_instance.is_course_staff(p.user) for p in students)
)
return ok, warnings + access_warnings, students
all_warnings = warnings + access_warnings
if not ok:
if len(all_warnings) == 0:
all_warnings.append(_(
'Cannot submit exercise due to unknown reason. If you '
'think this is an error, please contact course staff.'))
return self.SUBMIT_STATUS.INVALID, all_warnings, students
return self.SUBMIT_STATUS.ALLOWED, all_warnings, students

def _detect_group_changes(self, profile, group, submission):
submitters = list(submission.submitters.all())
Expand Down
11 changes: 6 additions & 5 deletions exercise/static/exercise/chapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
if (this.exercisesSize > 0) {
this.nextExercise();
} else {
$.augmentExerciseGroup($(".exercise-column"));
$.augmentSubmitButton($(".exercise-column"));
}
},

Expand Down Expand Up @@ -356,7 +356,7 @@

bindFormEvents: function(content) {
if (!this.ajax) {
var forms = content.find("form").attr("action", this.url);
var forms = content.find("form[data-aplus-overlay!='true']").attr("action", this.url);
var exercise = this;
if (this.chapter.ajaxForms) {
forms.on("submit", function(event) {
Expand All @@ -366,7 +366,7 @@
}
}

$.augmentExerciseGroup(content);
$.augmentSubmitButton(content);
this.element
.find(this.settings.external_launcher_selector)
.aplusExternalLauncher();
Expand Down Expand Up @@ -679,8 +679,9 @@
.empty().append(
$(data).filter(exercise.settings.exercise_selector).contents()
);
//f.find("table.submission-info").remove();
exercise.bindFormEvents(f);
// TODO: remove magic constant (variable defined in group.js)
f.removeClass('group-augmented');
exercise.bindFormEvents(exercise.element);
} else {
// Update the output box values
exercise.updateOutput(data.feedback);
Expand Down
12 changes: 8 additions & 4 deletions exercise/static/exercise/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,18 @@
}
});
}

object
.find('[data-aplus-disable-submit="true"] :submit')
.prop('disabled', true);
}
});

$.augmentExerciseGroup = function(object) {
if ($.augmentExerciseGroup_class === undefined) {
$.augmentExerciseGroup_class = new AplusExerciseGroup();
$.augmentSubmitButton = function(object) {
if ($.augmentSubmitButton_class === undefined) {
$.augmentSubmitButton_class = new AplusExerciseGroup();
}
$.augmentExerciseGroup_class.decorate(object);
$.augmentSubmitButton_class.decorate(object);
};

})(jQuery, window, document);
48 changes: 48 additions & 0 deletions exercise/templates/exercise/_warnings_overlay.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{% load i18n %}
{% load course %}
<div class="overlay exercise-warnings-overlay" id="overlay-{{ exercise.id }}">
<div class="panel panel-warning">
<div class="panel-heading">
{% trans "You cannot submit this exercise" %}
</div>
<div class="panel-body">
{% for issue in issues %}
<p>{{ issue }}</p>
{% endfor %}
<div class="btn-toolbar" role="toolbar">
<button
type="button"
class="btn btn-default"
id="overlay-dismiss-{{ exercise.id }}"
>
{% trans "Show anyway" %}
</button>
{% if should_enroll and enrollable and instance.is_enrollment_open %}
{# TODO: Remove partial duplication with course/course.html #}
<form
method="post"
action="{{ instance|url:'enroll' }}"
data-aplus-overlay="true"
>
{% csrf_token %}
<button
type="submit"
class="btn btn-info"
id="overlay-enroll-{{ exercise.id }}"
>
{% trans 'Enroll' %}
</button>
</form>
{% endif %}
</div>
</div>
</div>
<script>
$(function () {
$('#overlay-dismiss-{{ exercise.id }}').on('click', function () {
$('#overlay-{{ exercise.id }}').remove();
return false;
});
});
</script>
</div>
23 changes: 21 additions & 2 deletions exercise/templates/exercise/exercise.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,27 @@
{% include 'exercise/_user_toc.html' %}
{% endif %}

<div id="exercise-page-content"{% if exercise.is_submittable %} data-aplus-chapter="{{ exercise|url }}" data-aplus-group="{{ exercise.min_group_size }}-{{ exercise.max_group_size }}"{% if summary.get_submission_count > 0 %} data-aplus-group-fixed="{{ summary.get_group_id }}"{% endif %}{% endif %}>
{{ page.content|safe }}
<div
id="exercise-page-content"
{% if exercise.is_submittable %}
data-aplus-chapter="{{ exercise|url }}"
data-aplus-group="{{ exercise.min_group_size }}-{{ exercise.max_group_size }}"
{% if summary.get_submission_count > 0 %}
data-aplus-group-fixed="{{ summary.get_group_id }}"
{% endif %}
{% endif %}
>
<div class="overlay-parent">
{% if issues and not submission_allowed %}
{% include 'exercise/_warnings_overlay.html' %}
{% endif %}
<div
{% if disable_submit %}
data-aplus-disable-submit="true"
{% endif %}
>
{{ page.content|safe }}
</div>
</div>

<div id="submit-progress" class="hide progress">
Expand Down
Loading

0 comments on commit 1c7bcee

Please sign in to comment.