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": "", "type": "user_confirmation", "value": { "text": "Ja, ich habe Motorfahrzeugversicherungspolice..." } }, { "id": "", "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 filter_evaluation_tasks(self): return self.evaluation_tasks.raw_data AssignmentCompletionStatus = Enum( "AssignmentCompletionStatus", ["in_progress", "submitted", "evaluation_in_progress", "evaluation_submitted"], ) 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) evaluated_at = models.DateTimeField(null=True, blank=True) evaluation_user = models.ForeignKey( User, on_delete=models.CASCADE, null=True, blank=True, related_name="+", ) 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.value, 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="")