306 lines
9.0 KiB
Python
306 lines
9.0 KiB
Python
from enum import Enum
|
|
|
|
from django.db import models
|
|
from django.db.models import UniqueConstraint
|
|
from slugify import slugify
|
|
from wagtail import blocks
|
|
from wagtail.admin.panels import FieldPanel
|
|
from wagtail.fields import RichTextField, StreamField
|
|
from wagtail.models import Page
|
|
|
|
from vbv_lernwelt.core.constants import DEFAULT_RICH_TEXT_FEATURES
|
|
from vbv_lernwelt.core.model_utils import find_available_slug
|
|
from vbv_lernwelt.core.models import User
|
|
from vbv_lernwelt.course.models import CourseBasePage
|
|
|
|
|
|
class AssignmentListPage(CourseBasePage):
|
|
subpage_types = ["assignment.Assignment"]
|
|
parent_page_types = ["course.CoursePage"]
|
|
|
|
def save(self, clean=True, user=None, log_action=False, **kwargs):
|
|
self.slug = find_available_slug(
|
|
slugify(f"{self.get_parent().slug}-assignment", allow_unicode=True),
|
|
ignore_page_id=self.id,
|
|
)
|
|
super(AssignmentListPage, self).save(clean, user, log_action, **kwargs)
|
|
|
|
def __str__(self):
|
|
return f"{self.title}"
|
|
|
|
|
|
class ExplanationBlock(blocks.StructBlock):
|
|
text = blocks.RichTextBlock(features=DEFAULT_RICH_TEXT_FEATURES)
|
|
|
|
class Meta:
|
|
icon = "comment"
|
|
|
|
|
|
class PerformanceObjectiveBlock(blocks.StructBlock):
|
|
text = blocks.TextBlock()
|
|
|
|
class Meta:
|
|
icon = "tick"
|
|
|
|
|
|
class UserTextInputBlock(blocks.StructBlock):
|
|
text = blocks.RichTextBlock(
|
|
blank=True, required=False, features=DEFAULT_RICH_TEXT_FEATURES
|
|
)
|
|
|
|
class Meta:
|
|
icon = "edit"
|
|
|
|
|
|
class UserConfirmationBlock(blocks.StructBlock):
|
|
text = blocks.RichTextBlock(features=DEFAULT_RICH_TEXT_FEATURES)
|
|
|
|
class Meta:
|
|
icon = "tick-inverse"
|
|
|
|
|
|
class TaskContentStreamBlock(blocks.StreamBlock):
|
|
explanation = ExplanationBlock()
|
|
user_text_input = UserTextInputBlock()
|
|
user_confirmation = UserConfirmationBlock()
|
|
|
|
|
|
class TaskBlock(blocks.StructBlock):
|
|
title = blocks.TextBlock()
|
|
file_submission_required = blocks.BooleanBlock(required=False)
|
|
content = TaskContentStreamBlock(
|
|
blank=True,
|
|
)
|
|
|
|
class Meta:
|
|
icon = "tasks"
|
|
label = "Teilauftrag"
|
|
|
|
|
|
class EvaluationSubTaskBlock(blocks.StructBlock):
|
|
title = blocks.TextBlock()
|
|
description = blocks.RichTextBlock(
|
|
blank=True, required=False, features=DEFAULT_RICH_TEXT_FEATURES
|
|
)
|
|
points = blocks.IntegerBlock()
|
|
|
|
class Meta:
|
|
icon = "tick"
|
|
label = "Beurteilung"
|
|
|
|
|
|
class EvaluationTaskBlock(blocks.StructBlock):
|
|
title = blocks.TextBlock()
|
|
description = blocks.RichTextBlock(
|
|
blank=True, required=False, features=DEFAULT_RICH_TEXT_FEATURES
|
|
)
|
|
max_points = blocks.IntegerBlock()
|
|
sub_tasks = blocks.ListBlock(
|
|
EvaluationSubTaskBlock(), blank=True, use_json_field=True
|
|
)
|
|
|
|
class Meta:
|
|
icon = "tasks"
|
|
label = "Beurteilungskriterium"
|
|
|
|
|
|
class Assignment(CourseBasePage):
|
|
serialize_field_names = [
|
|
"starting_position",
|
|
"effort_required",
|
|
"performance_objectives",
|
|
"evaluation_description",
|
|
"evaluation_document_url",
|
|
"tasks",
|
|
"evaluation_tasks",
|
|
]
|
|
|
|
starting_position = RichTextField(
|
|
help_text="Erläuterung der Ausgangslage", features=DEFAULT_RICH_TEXT_FEATURES
|
|
)
|
|
effort_required = models.CharField(
|
|
max_length=100, help_text="Zeitaufwand als Text", blank=True
|
|
)
|
|
|
|
performance_objectives = StreamField(
|
|
[
|
|
("performance_objective", PerformanceObjectiveBlock()),
|
|
],
|
|
use_json_field=True,
|
|
blank=True,
|
|
help_text="Leistungsziele des Auftrags",
|
|
)
|
|
|
|
tasks = StreamField(
|
|
[
|
|
("task", TaskBlock()),
|
|
],
|
|
use_json_field=True,
|
|
blank=True,
|
|
help_text="Teilaufgaben",
|
|
)
|
|
|
|
evaluation_description = RichTextField(
|
|
blank=True,
|
|
help_text="Beschreibung der Bewertung",
|
|
features=DEFAULT_RICH_TEXT_FEATURES,
|
|
)
|
|
evaluation_document_url = models.CharField(
|
|
max_length=255,
|
|
blank=True,
|
|
help_text="URL zum Beurteilungsinstrument",
|
|
)
|
|
|
|
evaluation_tasks = StreamField(
|
|
[
|
|
("task", EvaluationTaskBlock()),
|
|
],
|
|
use_json_field=True,
|
|
blank=True,
|
|
help_text="Beurteilungsschritte",
|
|
)
|
|
|
|
content_panels = Page.content_panels + [
|
|
FieldPanel("starting_position"),
|
|
FieldPanel("effort_required"),
|
|
FieldPanel("performance_objectives"),
|
|
FieldPanel("tasks"),
|
|
FieldPanel("evaluation_description"),
|
|
FieldPanel("evaluation_document_url"),
|
|
FieldPanel("evaluation_tasks"),
|
|
]
|
|
|
|
subpage_types = []
|
|
|
|
class Meta:
|
|
verbose_name = "Auftrag"
|
|
|
|
def save(self, clean=True, user=None, log_action=False, **kwargs):
|
|
self.slug = find_available_slug(
|
|
slugify(f"{self.get_parent().slug}-{self.title}", allow_unicode=True),
|
|
ignore_page_id=self.id,
|
|
)
|
|
super(Assignment, self).save(clean, user, log_action, **kwargs)
|
|
|
|
def filter_user_subtasks(self, subtask_types=None):
|
|
"""
|
|
Filters out all the subtasks which require user input
|
|
:param subtask_types:
|
|
:return: list of subtasks with the shape: [
|
|
{
|
|
"id": "<uuid>",
|
|
"type": "user_confirmation",
|
|
"value": {
|
|
"text": "Ja, ich habe Motorfahrzeugversicherungspolice..."
|
|
}
|
|
},
|
|
{
|
|
"id": "<uuid>",
|
|
"type": "user_text_input",
|
|
"value": {
|
|
"text": "Gibt es zusätzliche Deckungen, die du der Person empfehlen..."
|
|
},
|
|
]
|
|
"""
|
|
if subtask_types is None:
|
|
subtask_types = ["user_text_input", "user_confirmation"]
|
|
|
|
raw_tasks = self.tasks.raw_data
|
|
|
|
return [
|
|
sub_dict
|
|
for task_dict in raw_tasks
|
|
for sub_dict in task_dict["value"]["content"]
|
|
if sub_dict["type"] in subtask_types
|
|
]
|
|
|
|
def get_evaluation_tasks(self):
|
|
return [task for task in self.evaluation_tasks.raw_data]
|
|
|
|
def get_input_tasks(self):
|
|
return self.filter_user_subtasks() + self.get_evaluation_tasks()
|
|
|
|
|
|
AssignmentCompletionStatus = Enum(
|
|
"AssignmentCompletionStatus",
|
|
["IN_PROGRESS", "SUBMITTED", "EVALUATION_IN_PROGRESS", "EVALUATION_SUBMITTED"],
|
|
)
|
|
|
|
|
|
def is_valid_assignment_completion_status(status):
|
|
return status in AssignmentCompletionStatus.__members__
|
|
|
|
|
|
class AssignmentCompletion(models.Model):
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
submitted_at = models.DateTimeField(null=True, blank=True)
|
|
evaluation_submitted_at = models.DateTimeField(null=True, blank=True)
|
|
evaluation_user = models.ForeignKey(
|
|
User,
|
|
on_delete=models.CASCADE,
|
|
null=True,
|
|
blank=True,
|
|
related_name="+",
|
|
)
|
|
evaluation_grade = models.FloatField(null=True, blank=True)
|
|
evaluation_points = models.FloatField(null=True, blank=True)
|
|
|
|
assignment_user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
|
|
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
|
|
|
|
completion_status = models.CharField(
|
|
max_length=255,
|
|
choices=[(acs.name, acs.name) for acs in AssignmentCompletionStatus],
|
|
default="IN_PROGRESS",
|
|
)
|
|
|
|
completion_data = models.JSONField(default=dict)
|
|
additional_json_data = models.JSONField(default=dict)
|
|
|
|
class Meta:
|
|
constraints = [
|
|
UniqueConstraint(
|
|
fields=["assignment_user", "assignment", "course_session"],
|
|
name="assignment_completion_unique_user_assignment_course_session",
|
|
)
|
|
]
|
|
|
|
|
|
class AssignmentCompletionAuditLog(models.Model):
|
|
"""
|
|
This model is used to store the "SUBMITTED" and "EVALUATION_SUBMITTED" data separately
|
|
"""
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
evaluation_user = models.ForeignKey(
|
|
User, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
|
|
)
|
|
assignment_user = models.ForeignKey(
|
|
User, on_delete=models.SET_NULL, null=True, related_name="+"
|
|
)
|
|
assignment = models.ForeignKey(
|
|
Assignment, on_delete=models.SET_NULL, null=True, related_name="+"
|
|
)
|
|
course_session = models.ForeignKey(
|
|
"course.CourseSession", on_delete=models.SET_NULL, null=True, related_name="+"
|
|
)
|
|
|
|
completion_status = models.CharField(
|
|
max_length=255,
|
|
choices=[(acs.value, acs.name) for acs in AssignmentCompletionStatus],
|
|
default="IN_PROGRESS",
|
|
)
|
|
|
|
completion_data = models.JSONField(default=dict)
|
|
additional_json_data = models.JSONField(default=dict)
|
|
|
|
assignment_user_email = models.CharField(max_length=255)
|
|
assignment_slug = models.CharField(max_length=255)
|
|
evaluation_user_email = models.CharField(max_length=255, blank=True, default="")
|
|
evaluation_grade = models.FloatField(null=True, blank=True)
|
|
evaluation_points = models.FloatField(null=True, blank=True)
|