devilry_qualifiesforexam2

Database models, APIs and UI for qualifying students for final exams.

UI workflow

Step 1, 3 and 4 is provided with the app, and step 2 is the actual “plugin” that’s implemented, but more on that further down in the Creating a plugin-section.

The numbered list below shows the preferable UI-workflow of a plugin.

  1. Choose a plugin:

    List the title and description of each plugin and let the user select the plugin they want to use.

  2. Plugin(this is usually the only part that needs to be implemented):

    This part is completely controlled by the plugin and may be more than one page if that should be needed by the plugin. This is where you crunch the data needed to display the final result.

  3. Preview:

    Preview the results with the option to save or go back to the previous page.

  4. Final state:

    When the preview is saved, a new view with the result should be shown. This view may “look” the same as the preview view. I most cases this will be a list of all students and their current status on whether they are qualified or not and status for each assignment.

Creating a plugin

Using the already existing approved/not approved plugin as an example. The plugin shows a view where you can select the assignments that needs to be approved for a student to qualify for the final exam.

A plugin can do whatever fits its purpose, just remember to follow the workflow above.

1: In you app create a view or views for the plugin. Using a multiselect view which lists all the assignments, and makes it possible to either select or deselect each assignment. You must create a form to be used, and the actual view to use by extending the needed classes provided in devilry.devilry_qualifiesforexam.views.plugin_base_views.base_multiselect_view.

# Devilry imports
from devilry.devilry_qualifiesforexam.views.plugin_base_views import base_multiselect_view
from devilry.devilry_qualifiesforexam_plugin_approved import resultscollector
from devilry.devilry_qualifiesforexam.views import plugin_mixin


class PluginSelectAssignmentsView(base_multiselect_view.QualificationItemListView, plugin_mixin.PluginMixin):
    plugintypeid = 'devilry_qualifiesforexam_plugin_approved.plugin_select_assignments'

    def get_period_result_collector_class(self):
        return resultscollector.PeriodResultSetCollector

2: As you can see, we have defined a function get_period_result_collector_class in the class defined in step 1. If you need to base the qualification status of a student on the achievements on assignments, you will need to create a class for your plugin that extends devilry.devilry_qualifiesforexam.pluginhelpers.PeriodResultSet (as we need to do in this example).

# Devilry imports
from devilry.devilry_qualifiesforexam.pluginhelpers import PeriodResultsCollector


class PeriodResultSetCollector(PeriodResultsCollector):
    """
    A subset of assignments are evaluated for the period.
    """
    def student_qualifies_for_exam(self, aggregated_relstudentinfo):
        for assignmentid, groupfeedbacksetlist in aggregated_relstudentinfo.assignments.iteritems():
            if assignmentid in self.qualifying_assignment_ids or len(self.qualifying_assignment_ids) == 0:
                feedbackset = groupfeedbacksetlist.get_feedbackset_with_most_points()
                if not feedbackset:
                    return False
                elif feedbackset.grading_points < feedbackset.group.parentnode.passing_grade_min_points:
                    return False
        return True

3: In your app, create a plugin.py file. Inside the plugin.py create a class that extends devilry.devilry_qualifiesforexam.plugintyperegistry.PluginType. This defines the plugin you just created the view logic for in step 1.

from devilry.devilry_qualifiesforexam.plugintyperegistry import PluginType
from .views import select_assignment

class SelectAssignmentsPlugin(PluginType):
    plugintypeid = 'your_app_name.plugin_pluginname'
    human_readable_name = 'Select assignments'
    description = 'Some description of what the plugins does.'

    def get_plugin_view_class(self):
        return select_assignment.PluginSelectAssignmentsView

4: Register the plugin with the devilry_qualifiesforexam app by creating an apps.py file and load the plugin to the registry when the app is loaded.

from django.apps import AppConfig


class DevilryQualifiesForExamAppConfig(AppConfig):
    name = 'devilry.devilry_qualifiesforexam_plugin_approved'
    verbose_name = 'Devilry qualifies for exam plugin approved'

    def ready(self):
        from devilry.devilry_qualifiesforexam import plugintyperegistry
        from . import plugin
        plugintyperegistry.Registry.get_instance().add(plugin.SelectAssignmentsPlugin)

Datamodel API

class devilry.devilry_qualifiesforexam.models.DeadlineTag(id, timestamp, tag)
exception DoesNotExist
exception MultipleObjectsReturned
class devilry.devilry_qualifiesforexam.models.PeriodTag(period, deadlinetag)
exception DoesNotExist
exception MultipleObjectsReturned
class devilry.devilry_qualifiesforexam.models.StatusQuerySet(model=None, query=None, using=None, hints=None)

QuerySet-manager for Status.

get_last_status_in_period(period, prefetch_relations=True)

Get the last Status for the period.

Prefetches all QualifiesForFinalExam for the Status objects and selects the last status.

Parameters:
  • period – current period.

  • prefetch_relations – Prefetches the period for the Status and the related QualifiesForFinalExam.

Returns:

The latest Status.

class devilry.devilry_qualifiesforexam.models.Status(id, status, period, createtime, message, user, plugin, plugin_data, exported_timestamp)
objects = <django.db.models.manager.ManagerFromStatusQuerySet object>

Sets StatusQuerySet as manager for model.

READY = 'ready'

The list of qualified students are ready for export. Usually this means that all students have received a feedback on their assignments.

This should be the default when creating a new Status for a Period.

ALMOSTREADY = 'almostready'

Most students are ready for export. Almost all students have received a feedback on their assignments. TODO: Legacy, to be removed.

NOTREADY = 'notready'

Students have pending feedbacks. This should be used when students students are awaiting feedback on assignments. Typically the status is not ready if no students(or just a few) have received a feedback on the last assignment.

STATUS_CHOICES = [('ready', 'Ready for export'), ('almostready', 'Most students are ready for export'), ('notready', 'Not ready for export (retracted)')]

Choice list for status on the qualification list.

status

The status of the qualification list. Note: The statuses does not represent any final state in the system, and the admin can use these statuses as they see fit

period

Period the qualifications are for.

createtime

Status created datetime. This is changed if the list updated.

message

Message provided with the qualification list of why it was retracted.

user

The user that created the qualification list(an admin).

plugin

The plugin used.

plugin_data

Plugin related data.

exported_timestamp

The datetime when the list was exported. If this is null, this is considered a draft for preview before it’s saved.

clean()

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

exception DoesNotExist
exception MultipleObjectsReturned
class devilry.devilry_qualifiesforexam.models.QualifiesForFinalExam(id, relatedstudent, status, qualifies)
relatedstudent

The related RelatedStudent the qualification is for.

status

The related Status for this student.

qualifies

True if the student qualifies for the exam, else False.

clean()

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

exception DoesNotExist
exception MultipleObjectsReturned
class devilry.devilry_qualifiesforexam.models.DeletedQualifiesForFinalExam(id, relatedstudent, status, qualifies)
relatedstudent

The related RelatedStudent the qualification is for.

status

The related Status for this student.

qualifies

True if the student qualifies for the exam, else False.

exception DoesNotExist
exception MultipleObjectsReturned

Views API

class devilry.devilry_qualifiesforexam.views.plugin_base_views.base_multiselect_view.SelectedQualificationForm(*args, **kwargs)

Subclass this if extra fields needs to be added.

qualification_modelclass

alias of Assignment

selected_items

The items selected as ModelMultipleChoiceField. If some or all items should be selected by default, override this.

property media

Return all media required to render the widgets on this form.

class devilry.devilry_qualifiesforexam.views.plugin_base_views.base_multiselect_view.SelectedQualificationItem(value)

The selected item from the list.

This can be subclassed if needed.

Parameters:

value – The value to render. Typically a django model object.

get_title()

Get the title of an item.

Returns:

SelectedQualificationItem.value string representation.

Return type:

str

class devilry.devilry_qualifiesforexam.views.plugin_base_views.base_multiselect_view.SelectableQualificationItemValue(*args, **kwargs)
Parameters:
  • target_dom_id – See get_target_dom_id().

  • is_selected – Mark the item as selected on load.

selected_item_renderer_class

alias of SelectedQualificationItem

get_title()

Get the title of the box.

Defaults to str(self.value).

get_description()

Get the description (shown below the title).

Defaults to None, which means that no description is rendered.

class devilry.devilry_qualifiesforexam.views.plugin_base_views.base_multiselect_view.QualificationItemTargetRenderer(form, dom_id=None, without_items_text=None, with_items_title=None, no_items_selected_text=None, submit_button_text=None, form_action=None, empty_selection_allowed=False)

Target renderer for selected items.

Provides default descriptions for target status when no items are selected, items are selected and the submit button text.

Subclass this if changes to these labels need to be made.

Parameters:
selected_target_renderer

The selected item as it is shown when selected. By default this is SelectedQualificationItem.

alias of SelectedQualificationItem

descriptive_item_name = 'assignments'

A descriptive name for the items selected.

get_submit_button_text()
Returns:

The text that should be shown on the submit button.

Return type:

str

get_with_items_title()
Returns:

The text that should be shown when items are selected.

Return type:

str

get_without_items_text()
Returns:

The text that should be shown when no items are selected.

Return type:

str

class devilry.devilry_qualifiesforexam.views.plugin_base_views.base_multiselect_view.QualificationItemListView(**kwargs)

This class provides a basic multiselect preset.

By default, this class uses Assignment as model. This means that this class lists all the Assignments for the Period as selectable items and all items are selected by default.

If you only want to list Assignment as selectable items and no extra fields needs to be added to the form, just subclass this and override get_period_result_collector_class().

Examples

Here is an example of a plugin view that uses Assignment as listing values and nothing else:

from devilry.devilry_qualifiesforexam.views.plugin_base_views import base_multiselect_view
from devilry.devilry_qualifiesforexam.views import plugin_mixin


class PluginSelectAssignmentsView(base_multiselect_view.QualificationItemListView, plugin_mixin.PluginMixin):
    plugintypeid = 'devilry_qualifiesforexam_plugin_approved.plugin_select_assignments'

    def get_period_result_collector_class(self):
        return some_result_collector_for_the_plugin

Here is an example that uses Assignment as selectable items but an extra field is added, to achieve this, the base form and target renderer must be subclassed:

from devilry.devilry_qualifiesforexam.views.plugin_base_views import base_multiselect_view
from devilry.devilry_qualifiesforexam_plugin_points import resultscollector
from devilry.devilry_qualifiesforexam.views import plugin_mixin


class PluginSelectAssignmentsAndPoints(base_multiselect_view.SelectedQualificationForm):
    min_points_to_achieve = forms.IntegerField(
            min_value=0,
            required=False,
    )


class WithPointsFormDataTargetRenderer(base_multiselect_view.QualificationItemTargetRenderer):
    def get_field_layout(self):
        return [
            'min_points_to_achieve'
        ]


class PluginSelectAssignmentsAndPointsView(base_multiselect_view.QualificationItemListView, plugin_mixin.PluginMixin):
    plugintypeid = 'devilry_qualifiesforexam_plugin_approved.plugin_select_assignments_and_points'

    def get_period_result_collector_class(self):
        return resultscollector.PeriodResultSetCollector

    def get_form_class(self):
        return PluginSelectAssignmentsAndPoints

    def get_target_renderer_class(self):
        return WithPointsFormDataTargetRenderer

Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.

model

The model represented as a selectable item.

alias of Assignment

value_renderer_class

alias of SelectableQualificationItemValue

dispatch(request, *args, **kwargs)

Check if a Status with status set to ready exists for the period. If it exists, redirect to the final export view.

get_queryset_for_role(role)

Get a queryset of all the objects for the QualificationItemListView.model .

This can be be customized with a call to super, and the filtering needed.

Parameters:

role – The cradmin_role(Period).

Returns:

queryset of from specified model.

Return type:

QuerySet

get_inititially_selected_queryset()

Get queryset of initially selected items.

Defaults to self.model.object.none(), and you should override this if you want to select any objects on load.

get_target_renderer_class()

Get the target renderer class.

If a subclass of QualificationItemTargetRenderer is created, override this method by returning the subclass.

Returns:

QualificationItemTargetRenderer

get_form_class()

Get a subclass of SelectedQualificationForm.

Must be implemented in subclass.

Raises:

NotImplementedError – If not implemented by subclass.

get_period_result_collector_class()

Must be implemented by subclass if needed.

Returns:

A subclass of devilry.devilry_qualifiesforexam.pluginshelper.PeriodResultsCollector

Raises:

NotImplementedError

get_qualifying_itemids(posted_form)

Get the ID of the items that qualify.

Parameters:

posted_form – The posted form containing the items selected.

Returns:

List of self.model.id that were selected.

get_form_kwargs()

Return the keyword arguments for instantiating the form.

form_valid(form)

Provides some basic functionality. If custom fields are added to the form, this function must be overridden in subclass to handle the posted data.

Parameters:

form – Posted form with ids of selected items.