Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Assessments app Initial model structure #2

Open
wants to merge 2 commits into
base: features/assessments-app
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/assessments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Assessments App
8 changes: 8 additions & 0 deletions apps/assessments/choices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# HTK Imports
from htk.apps.assessments.enums import QuestionType
from htk.utils.enums import get_enum_choices


def get_question_type_choices(include_hidden=True):
choices = get_enum_choices(QuestionType, include_hidden=include_hidden)
return choices
4 changes: 4 additions & 0 deletions apps/assessments/constants/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HTK_ASSESSMENTS_ASSESSMENT_MODEL = 'htk.Assessment'
HTK_ASSESSMENTS_QUESTION_MODEL = 'htk.Question'
HTK_ASSESSMENTS_QUESTION_CHOICE_MODEL = 'htk.QuestionChoice'
HTK_ASSESSMENTS_ANSWER_MODEL = 'htk.Answer'
13 changes: 13 additions & 0 deletions apps/assessments/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# HTK Imports
from htk.utils.enums import HtkEnum


class QuestionType(HtkEnum):
UNSPECIFIED = 0
# Answer is a free text response
BASIC = 1
# Multiple answer choices are presented, only one is correct
MULTI_CHOICE = 2
# Multiple answer choices are presented, any number (0 or many) can be correct,
# all correct responses must be selected
MULTI_CHOICE_MULTI_ANSWER = 4
Empty file.
22 changes: 22 additions & 0 deletions apps/assessments/models/answer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Django Imports
from django.db import models

# HTK Imports
from htk.apps.assessments.models.fk_fields import (
fk_question,
fk_question_choice,
)
from htk.models.classes import HtkBaseModel
from htk.models.fk_fields import fk_user


DEFAULT_RELATED_NAME = 'answers'


class Answer(HtkBaseModel):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class Assessment:
    @property
    def max_score(self):
        return self.questions.count()


class AssessmentQuestion:
    assessment: fk(Assesssment)


class AssessmentResponse
     assessment: fk
     user: fk

    @property
    def score(self):
        score = len([response for response in self.responses.all() if response.is_correct])
        return score

    @property
    def display_score(self):
        return f'{self.score} / {self.assessment.max_score}'

    @property
    def score_percentage(self):
        return self.score / self.assessment.max_score * 100


class AssessmentQuestionCorrectResponse:  # or `AnswerKey`?
     assessment: fk
     question(_id): fk, related_name="answer_key", unique=True  # (or maybe OneToOneField?)
     answer_text: str(correct answer)
     answer_choices: M2M(AnswerChoice)


class AssessmentQuestionResponse:
    assessment_response: fk(AssessmentResponse, related_name="responses")
    question: fk(the question this response is for)
    answer_text: str(user's text)
    answer_choices: M2M(AnswerChoice)

    def __init__(self, assessment_response):
        pass

    @property
    def is_correct(self):
         is_correct = True  # innocent until proven guilty

         answer_key = self.question.answer_key
         
         if is_correct and answer_key.answer_text != self.answer_text:
            is_correct = False

         if is_correct and answer_key.answer_choices != self.answer_choices:
            is_correct = False

        return is_correct

user = fk_user(DEFAULT_RELATED_NAME, required=True)
question = fk_question(DEFAULT_RELATED_NAME, required=True)
choice = fk_question_choice(DEFAULT_RELATED_NAME)

class Meta:
verbose_name = 'Assessment Question Answer'
21 changes: 21 additions & 0 deletions apps/assessments/models/answer_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Django Imports
from django.db import models

# HTK Imports
from htk.apps.assessments.models.fk_fields import (
fk_assessment,
fk_question,
)
from htk.models.classes import HtkBaseModel


DEFAULT_RELATED_NAME = 'answer_key'
DEFAULT_RELATED_NAME_PLURAL = 'answer_keys'


class AssessmentQuestionAnswerKey(HtkBaseModel):
assessment = fk_assessment(DEFAULT_RELATED_NAME_PLURAL, required=True)
question = fk_question(DEFAULT_RELATED_NAME, required=True, one_to_one=True)

class Meta:
verbose_name = 'Assessment Question Answer Key'
11 changes: 11 additions & 0 deletions apps/assessments/models/assessment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# HTK Imports
from htk.models.classes import HtkBaseModel


class Assessment(HtkBaseModel):
class Meta:
verbose_name = 'Assessment'

@property
def max_score(self):
return self.questions.count()
35 changes: 35 additions & 0 deletions apps/assessments/models/fk_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Django Imports
from django.db import models

# HTK Imports
from htk.models.fk_fields import build_kwargs
from htk.utils import htk_setting


def fk_assessment(related_name, required=False):
field = models.ForeignKey(
htk_setting('HTK_ASSESSMENTS_ASSESSMENT_MODEL'),
related_name=related_name,
**build_kwargs(required=required),
)
return field


def fk_question(related_name, required=False, one_to_one=False):
method_name = 'OneToOneField' if one_to_one else 'ForeignKey'
field_method = getattr(models, method_name)
field = field_method(
htk_setting('HTK_ASSESSMENTS_QUESTION_MODEL'),
related_name=related_name,
**build_kwargs(required=required),
)
return field


def fk_question_choice(related_name, required=False):
field = models.ForeignKey(
htk_setting('HTK_ASSESSMENTS_QUESTION_MODEL'),
related_name=related_name,
**build_kwargs(required=required),
)
return field
60 changes: 60 additions & 0 deletions apps/assessments/models/question.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Python Standard Library Imports
import random

# Django Imports
from django.db import models

# HTK Imports
from htk.apps.assessments.choices import get_question_type_choices
from htk.apps.assessments.enums import QuestionType
from htk.apps.assessments.models.fk_fields import fk_assessment
from htk.models.classes import HtkBaseModel


DEFAULT_RELATED_NAME = 'questions'


class AssessmentQuestion(HtkBaseModel):
assessment = fk_assessment(DEFAULT_RELATED_NAME, required=True)
type = models.PositiveIntegerField(
choices=get_question_type_choices(), default=QuestionType.UNSPECIFIED
)
text = models.TextField(max_length=512)
order = models.PositiveSmallIntegerField(default=0)
should_shuffle_choices = models.BooleanField(default=False)

class Meta:
verbose_name = 'Assessment Question'

##
# Properties

@property
def choices_ordered(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

answer_choices_**

choices = self.choices.order_by('order')
return choices

@property
def choices_shuffled(self):
choices = self.choices.all()
random.shuffle(choices)
return choices

@property
def all_choices(self):
choices = (
self.choices_shuffled
if self.should_shuffle_choices
else self.choices_ordered
)
return choices

##
# Helpers

def is_answered_by_user(self, user, check_correction=False):
answers = user.answers.filter(choice__question=self)
if check_correction:
answers = answers.filter(choice__is_correct=True)
is_answered = answers.exists()
return is_answered
17 changes: 17 additions & 0 deletions apps/assessments/models/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# HTK Imports
from htk.apps.assessments.models.fk_fields import (
fk_assessment,
fk_question,
)
from htk.models.classes import HtkBaseModel


DEFAULT_RELATED_NAME = 'responses'


class AssessmentResponse(HtkBaseModel):
assessment = fk_assessment(DEFAULT_RELATED_NAME, required=True)
question = fk_question(DEFAULT_RELATED_NAME, required=True)

class Meta:
verbose_name = 'Assessment Question Response'