devilry_qualifiesforexam¶
Database models, APIs and UI for qualifying students for final exams.
UI workflow¶
How users are qualified for final exam i plugin-based. The subject/period admin is taken through a wizard with the following steps/pages:
- If no configuration exists for the period:
List the title and description of each plugin (see Plugins below), and let the user select the plugin they want to use. The selection is stored in
QualifiesForFinalExamPeriodStatus.plugin.- If a configuration exists for the period:
Show the overview of the semester (basically the same as the preview described as page 3 below). Includes a button to change the configuration. Clicking this button will show the list of plugins, just like when no configuration exists, with the previously used plugin selected. The change-button is only available on active periods.
Completely controlled by the plugin. May be more than one page if that should be needed by the plugin. The plugin can also just redirect directly to the next page if it does not require any input from the user. We supply a box with save and back buttons that should be the same for all plugins.
Preview the results with the option to save or go back to the previous page.
Plugins¶
A plugin is a regular Django app. Your best source for a simple example is the
devilry_qualifiesforexam_approved-module which contains two plugins. You will find the
package in the src/-directory of the devilry repository.
The role of the plugin¶
A plugin is basically one or more Django views that, for the qualifies-for-exam system, acts like a black box with the following input and output:
The input is a dict store by the qualifies-for-exam system in the users session (
request.session):periodidThe ID of the class:devilry.apps.core.models.Period.
pluginsessionidAn ID that is generated by the qualifies-for-exam system. It is used to ensure that we do not get session key collisions when using the wizard from multiple browser windows at the same time.
The output is a
devilry_qualifiesforexam.pluginhelpers.PreviewData-object stored in the users session (request.session) under thequalifiesforexam-<pluginsessionid>key. The output object is used by the REST-api that generates the preview-data.
Registering an app as a qualifiesforexam plugin¶
Add something like the following to yourapp/devilry_plugin.py:
from devilry_qualifiesforexam.registry import qualifiesforexam_plugins
from django.urls import reverse
from django.utils.translation import gettext_lazy
qualifiesforexam_plugins.add(
id='myapp',
url=reverse('myapp-myplugin'), # The url of the view to use for step/page 2 in the workflow - the input parameters (see above) is added to this url.
title=gettext_lazy('My plugin'),
description=gettext_lazy('Does <strong>this</strong> and <em>that</em>.')
)
Create the view¶
See Plugin helpers and take a look at the sourcecode for
devilry_qualifiesforexam_approved (in the src/ directory of the Devilry sources).
Configure available plugins¶
Available plugins are configured in settings.DEVILRY_QUALIFIESFOREXAM_PLUGINS, which is
a list of plugin ids. Note that the apps containing the plugin must also be in
settings.INSTALLED_APPS, and the urls must be registered.
The plugins are shown in listed order on page 1 of the wizard described in the
UI workflow.
Note
You can safely remove plugins from settings.DEVILRY_QUALIFIESFOREXAM_PLUGINS.
They will simply not be available in the list of plugins in the
UI workflow.
Write tests¶
If you want your plugin to be considered for inclusion in Devilry you will have to write good
tests. These plugins handle very sensitive data, so it would be madness to deploy them in production
without proper tests.
See the tests-module in devilry_qualifiesforexam_approved for examples.
Plugin helpers¶
The mixin classes¶
QualifiesForExamPluginViewMixin is a mixin class
that simplifies the common tasks for all plugin views (getting input and setting
output).
Basic usage¶
Basic usage of the class turns the input and output steps described in
The role of the plugin into two methods:
get_plugin_input_and_authenticate(), save_plugin_output(). Those two
methods greatly simplify writing plugins. For example, we can create a view like this:
from django.views.generic import View
class MyPluginView(View, QualifiesForExamPluginViewMixin):
def post(self, request):
try:
self.get_plugin_input_and_authenticate()
except PermissionDenied:
return HttpResponseForbidden()
# Your code to detect passing students
passing_relatedstudentsids = [1,2,3]
self.save_plugin_output(passing_relatedstudentsids)
return HttpResponseRedirect(self.get_preview_url())
A more complete example¶
The example above is very simple. You will usually have to iterate over all the students in a period to find out who qualifies:
from django.views.generic import View
from devilry_qualifiesforexam.pluginhelpers import PeriodResultsCollector
from devilry_qualifiesforexam.pluginhelpers import QualifiesForExamPluginViewMixin
class MyPeriodResultsCollector(PeriodResultsCollector):
def student_qualifies_for_exam(self, aggregated_relstudentinfo):
# Test if the student in the AggreatedRelatedStudentInfo qualifies.
# Typically something like this (all students must pass all assignments):
for assignmentid, grouplist in aggregated_relstudentinfo.assignments.iteritems():
feedback = grouplist.get_feedback_with_most_points()
if not feedback or not feedback.is_passing_grade:
return False
return True
class MyPluginView(View, QualifiesForExamPluginViewMixin):
def post(self, request):
try:
self.get_plugin_input_and_authenticate()
except PermissionDenied:
return HttpResponseForbidden()
# Your code to detect passing students
passing_relatedstudentsids = MyPeriodResultsCollector().get_relatedstudents_that_qualify_for_exam()
self.save_plugin_output(passing_relatedstudentsids)
return HttpResponseRedirect(self.get_preview_url())
- class devilry_qualifiesforexam.pluginhelpers.QualifiesForExamPluginViewMixin¶
- periodid¶
The ID of the period — set by
get_plugin_input().
- period¶
The period object loaded using the
django.shortcuts.get_object_or_404()— set byget_plugin_input().
- pluginsessionid¶
The pluginsessionid described in The role of the plugin — set by
get_plugin_input().
- get_plugin_input_and_authenticate()¶
Reads the parameters (periodid and pluginsessionid) from the querystring and store them as in the following instance variables:
periodid,period,pluginsessionid.- Raise:
django.core.exceptions.PermissionDeniedif the request user is not administrator on the period.
- save_plugin_output(*args, **kwargs)¶
Shortcut that saves a
PreviewDatain the session key generated usingcreate_sessionkey(). Args and kwargs are forwarded toPreviewData.
- save_settings_in_session(settings)¶
Save settings in the session. You get this back as an argument to your
post_statussave-handler if your plugin is configured withuses_settings=True.
- get_preview_url()¶
Get the preview URL - the URL you must redirect to after saving the output (
save_plugin_output()) to proceed to the preview.
- get_selectplugin_url()¶
Get the preview URL - the URL you should navigate to when users select Back from your plugin view.
- redirect_to_preview_url()¶
Returns a
HttpResponseRedirectthat redirects toget_preview_url().
Helper for unit tests¶
Other helpers¶
- class devilry_qualifiesforexam.pluginhelpers.PreviewData(passing_relatedstudentids)¶
Stores the output from a plugin. You should not need to use this directly. Use
QualifiesForExamPluginViewMixin.save_plugin_output()instead.- Parameters:
passing_relatedstudentids – See
passing_relatedstudentids.
List of the IDs of all
devilry.apps.core.models.RelatedStudentthat qualifies for final exams according to the plugin that generated the data.
- devilry_qualifiesforexam.pluginhelpers.create_sessionkey(pluginsessionid)¶
Generate the session key for the plugin output as described in The role of the plugin. You should not need to use this directly. Use
QualifiesForExamPluginViewMixin.get_plugin_input_and_authenticate()instead.
Plugins shipped with Devilry¶
devilry_qualifiesforexam_approved¶
TODO
Database models¶
How the models fit together¶
Each time a periodadmin qualifies students for final exams, even when they only partly qualify their
students, a new Status-record is saved in the database. A status has a ForeignKey to
devilry.apps.core.models.Period, so the last saved Status is the active
qualified-for-exam status for a Period.
Each time a Status is saved, all of the devilry.apps.core.models.RelatedStudent`s
for that period gets a :class:.QualifiesForFinalExam`-record, which saves the qualifies-for-exam
status for the student. When a status is almostready, we use NULL in the
QualifiesForFinalExam.qualifies-field to indicate students that are not ready.
Node administrators or systems that intergrate with Devilry uses Status.exported_timestamp
to mark Status-records that have been exported to an external system. It is important to
note that we export statuses, not periods. This means that we can create new statuses, and re-export
them. An automatic system can check timestamps to handle status changes, and the Node admin UI
can show/hilight periods with exported statuses and more recent statuses.
DeadlineTag is used to organize periods by the time when they should have made a
ready-Status.
The models¶
- class devilry_qualifiesforexam.models.DeadlineTag¶
A deadlinetag is used to tag
devilry.apps.core.models.Period-objects with a timestamp and an optional tag describing the timestamp.- timestamp¶
Database field containing the date and time when a period admin should be finished qualifying students for final exams.
- tag¶
A tag for node-admins for this deadlinetag. Max 30 chars. May be empty or
null.
- class devilry_qualifiesforexam.models.PeriodTag¶
This table is used to create a one-to-many relation from
DeadlineTagtodevilry.apps.core.models.Period.- deadlinetag¶
Database foreign key to the
DeadlineTagthat the Period should be tagged by.
- period¶
Database foreign key to the
devilry.apps.core.models.Periodthat this tag points to.
- class devilry_qualifiesforexam.models.Status¶
Every time the admin updates qualifies-for-exam on a period, we save new object of this database model.
This gives us a history of changes, and it makes it possible for subject/period admins to communicate simple information to whoever it is that is responsible for handling examinations.
- period¶
Database foreign key to the
devilry.apps.core.models.Periodthat the status is for.
- exported_timestamp¶
Database datetime field that tells when the status was exported out of Devilry to an external system. This is
nullif the status has not been expored out of Devilry.
- status¶
Database char field that accepts the following values:
readyis used to indicate the the entire period is ready for export/use.almostreadyis used to indicate that the period is almost ready for export/use, and that the exceptions are explained in themessage.notreadyis used to indicate that the period has no useful data yet. This is typically only used when the period used to be ready or almostready, but had to be retracted for a reason explained in the status
- createtime¶
Database datetime field where we store when we added the status.
- message¶
Database field with an optional message about the status change.
- user¶
Database foreign key to the user that made the status change.
- class devilry_qualifiesforexam.models.QualifiesForFinalExam¶
Database one-to-one relation to
devilry.apps.core.models.RelatedStudent.
- qualifies¶
Boolean database field telling if the student qualifies or not. This may be
None(NULL), if the status isalmostready, to mark students as not ready for export.
- status¶
Foreign key to a
QualifiesForFinalExamPeriodStatus.