Skip to content

Commit

Permalink
improved models to manage variables
Browse files Browse the repository at this point in the history
  • Loading branch information
arielfayol37 committed Aug 21, 2023
1 parent 9bc53ff commit d348cda
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 17 deletions.
Binary file modified db.sqlite3
Binary file not shown.
Binary file modified deimos/__pycache__/models.cpython-311.pyc
Binary file not shown.
Binary file modified deimos/__pycache__/views.cpython-311.pyc
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.3 on 2023-08-21 19:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('phobos', '0022_variable_remove_hint_url_alter_question_answer_type_and_more'),
('deimos', '0007_rename_is_successful_questionattempt_success'),
]

operations = [
migrations.AddField(
model_name='questionstudent',
name='instances_created',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='questionstudent',
name='var_instances',
field=models.ManyToManyField(related_name='question_students', to='phobos.variableinstance'),
),
]
Binary file not shown.
41 changes: 35 additions & 6 deletions deimos/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db import models
from phobos.models import Course, Question, User, Assignment, VariableInstance
from phobos.models import Course, Question, User, Assignment, VariableInstance, QuestionChoices
from django.core.validators import MaxValueValidator, MinValueValidator
class Student(User):
"""
Expand Down Expand Up @@ -111,18 +111,47 @@ class QuestionStudent(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
num_points = models.FloatField(default=0)
success = models.BooleanField(default=False)
# Will probably never use that related name.
var_instances = models.ManyToManyField(VariableInstance, related_name='question_students')
def get_instances(self):
instances_created = models.BooleanField(default=False)
def create_instances(self):
"""
Get variable instances from the variables associated to the question.
"""
for var in self.question.variables:
self.var_instances.clear()
for var in self.question.variables.all():
self.var_instances.add(var.get_instance())

def compute_structural_answer(self):
"""
Computes the answer to the question if it's a `Question` with `Variable` answers.
"""
if not self.instances_created:
self.create_instances()
self.instances_created = True
if self.question.answer_type == QuestionChoices.STRUCTURAL_VARIABLE_FLOAT:
assert self.question.variable_float_answers.count() == 1
answer = self.question.variable_float_answers.first().content
for var_instance in self.var_instances:
answer = answer.replace(var_instance.variable.symbol, var_instance.value)
# TODO: Add a clause here if the answer type is different
# TODO: Add another clause here to make sure the answer evaluates to a float.
return eval(answer)
def compute_mcq_answers(self):
"""
Computes the float anwers to an MCQ question if they were variables.
"""
if self.question.answer_type.startswith('MCQ'):
mcq_var_floats = self.question.mcq_variable_float_answers.all()
answers = []
for mcq_var_float in mcq_var_floats:
answer = mcq_var_float.content
for var_instance in self.var_instances:
answer = answer.replace(var_instance.variable.symbol, var_instance.value)
answers.append(eval(answer))
return answers

def get_num_points(self):
"""
Calculates adn returns the number of points a student gets from a question
Calculates and returns the number of points a student gets from a question
"""
total = 0
for attempt in self.attempts:
Expand Down
9 changes: 8 additions & 1 deletion deimos/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,20 @@ def answer_question(request, question_id, assignment_id=None, course_id=None):
elif question.answer_type == QuestionChoices.STRUCTURAL_EXPRESSION:
answers.extend(question.expression_answers.all())
question_type = [0]
elif question.answer_type == QuestionChoices.STRUCTURAL_VARIABLE_FLOAT:
# TODO:!important ...extend the answers appropriately
answer = question_student.compute_structural_answer()
question_type = [5]
pass
elif question.answer_type == QuestionChoices.STRUCTURAL_FLOAT:
answers.extend(question.float_answers.all())
question_type = [1]
elif question.answer_type == QuestionChoices.STRUCTURAL_TEXT:
is_fr = True
answers.extend(question.text_answers.all())
question_type = [4]
question_type = [4]


context = {
'question':question,
'question_ids_nums':zip(question_ids, question_nums),
Expand Down
Binary file modified phobos/__pycache__/models.cpython-311.pyc
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Generated by Django 4.2.3 on 2023-08-21 19:48

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('phobos', '0021_question_margin_error_and_more'),
]

operations = [
migrations.CreateModel(
name='Variable',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=3)),
('instances_created', models.BooleanField(default=False)),
],
),
migrations.RemoveField(
model_name='hint',
name='url',
),
migrations.AlterField(
model_name='question',
name='answer_type',
field=models.CharField(choices=[('STRUCTURAL_EXPRESSION', 'Structural Expression'), ('STRUCTURAL_FLOAT', 'Structural Float'), ('STRUCTURAL_VARIABLE_FLOAT', 'Variable Float'), ('STRUCTURAL_TEXT', 'Structural Text'), ('STRUCTURAL_LATEX', 'Structural Latex'), ('SURVEY', 'Survey'), ('MCQ_EXPRESSION', 'MCQ Expression'), ('MCQ_FLOAT', 'MCQ Float'), ('MCQ_VARIABLE_FLOAT', 'MCQ Variable Float'), ('MCQ_LATEX', 'MCQ Latex'), ('MCQ_TEXT', 'MCQ Text')], default='STRUCTURAL_TEXT', max_length=30),
),
migrations.CreateModel(
name='VariableInterval',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lower_bound', models.FloatField(default=-999)),
('upper_bound', models.FloatField(default=999)),
('variable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='intervals', to='phobos.variable')),
],
),
migrations.CreateModel(
name='VariableInstance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.FloatField()),
('variable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='phobos.variable')),
],
),
migrations.CreateModel(
name='VariableFloatAnswer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.CharField(max_length=100)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variable_float_answers', to='phobos.question')),
],
),
migrations.AddField(
model_name='variable',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variables', to='phobos.question'),
),
migrations.CreateModel(
name='MCQVariableFloatAnswer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.CharField(max_length=100)),
('is_answer', models.BooleanField(default=False)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mcq_variable_float_answers', to='phobos.question')),
],
),
]
Binary file not shown.
69 changes: 59 additions & 10 deletions phobos/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ class SubjectChoices(models.TextChoices):
class QuestionChoices(models.TextChoices):
STRUCTURAL_EXPRESSION = 'STRUCTURAL_EXPRESSION', 'Structural Expression'
STRUCTURAL_FLOAT = 'STRUCTURAL_FLOAT', 'Structural Float'
STRUCTURAL_VARIABLE_FLOAT = 'STRUCTURAL_VARIABLE_FLOAT', 'Variable Float'
STRUCTURAL_TEXT = 'STRUCTURAL_TEXT', 'Structural Text'
STRUCTURAL_LATEX = 'STRUCTURAL_LATEX', 'Structural Latex'
SURVEY = 'SURVEY', 'Survey'
# The following MCQ variations are useless because a `Question`
# may have different types of MCQ answers. So at the end of the
# day, we just check if it's an MCQ and check all the types.
MCQ_EXPRESSION = 'MCQ_EXPRESSION', 'MCQ Expression'
MCQ_FLOAT = 'MCQ_FLOAT', 'MCQ Float'
MCQ_VARIABLE_FLOAT = 'MCQ_VARIABLE_FLOAT', 'MCQ Variable Float'
MCQ_LATEX = 'MCQ_LATEX', 'MCQ Latex'
MCQ_TEXT = 'MCQ_TEXT', 'MCQ Text'
SURVEY = 'SURVEY', 'Survey'


class AssignmentChoices(models.TextChoices):
QUIZ = 'QUIZ', 'Quiz'
Expand Down Expand Up @@ -192,9 +198,8 @@ class Hint(models.Model):
"""
Each question/subquestion may have hints in the form of text or urls to help.
"""
text = models.TextField(blank=False, null=False) # Professor must include text(description)
url = models.URLField(blank=True, null=True)
question = models.ForeignKey(Question, related_name='hints', on_delete=models.CASCADE)
text = models.TextField(blank=False, null=False) # Professor must include text(description)

def __str__(self):
return f"Hint {self.id} for {self.question}"
Expand All @@ -210,6 +215,18 @@ class FloatAnswer(models.Model):
def __str__(self):

return f"Float Answer for {self.question}: {self.content}"

class VariableFloatAnswer(models.Model):
"""
Answer to a `structural Question` or `MCQ Question` may be a variable float.
E.g answer = F/m, where F and m are going to have multiple different values assigned
to different users.
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='variable_float_answers')
content = models.CharField(max_length=100, null=False, blank=False)

def __str__(self):
return f"Variable Float answer for {self.question}: {self.content}"


class ExpressionAnswer(models.Model):
Expand Down Expand Up @@ -250,7 +267,7 @@ def __str__(self):

class MCQFloatAnswer(models.Model):
"""
Answer to a `structural Question` may be an algebraic expression, a vector, or a float.
Float Answer to a `MCQ Question`.
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="mcq_float_answers")
content = models.FloatField(blank=False, null=False)
Expand All @@ -262,12 +279,26 @@ def __str__(self):
else:
return f"Incorrect MCQ Float Answer for {self.question}: {self.content}"

class MCQVariableFloatAnswer(models.Model):
"""
Answer to a `structural Question` or `MCQ Question` may be a variable float.
E.g answer = F/m, where F and m are going to have multiple different values assigned
to different users.
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='mcq_variable_float_answers')
content = models.CharField(max_length=100, null=False, blank=False)
is_answer = models.BooleanField(default=False)

def __str__(self):
if self.is_answer:
return f"Correct MCQ Variable Float Answer for {self.question}: {self.content}"
else:
return f"Incorrect MCQ Variable Float Answer for {self.question}: {self.content}"


class MCQExpressionAnswer(models.Model):
"""
An expression for a `structural Question` may just be interpreted as text. The math.js library
will parse the expression given by the teacher and the resulting text will be stored.
When a user will input an answer, it will be compared to that text.
Expression answer for a `MCQ Question`.
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="mcq_expression_answers")
content = models.CharField(max_length=200)
Expand Down Expand Up @@ -331,31 +362,49 @@ class Variable(models.Model):
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='variables')
symbol = models.CharField(max_length=3, blank=False, null=False)
instances_created = models.BooleanField(default=False)

def __str__(self):
return f"Variable `{self.symbol}` for question {self.question}"
def create_instances(self, num, lower_bound, upper_bound, is_int=False):
def create_instances(self, num = 5, is_int=False):
"""
Creates num number of random variable instances.
"""
intervals = self.intervals.all()
for i in num:
bounds = random.choice(intervals)
lower_bound = bounds.lower_bound
upper_bound = bounds.upper_bound
random_float = random.uniform(lower_bound, upper_bound)
if is_int:
random_float = float(int(random_float))
vi = VariableInstance.objects.create(variable=self, value=random_float)
vi.save()
def get_instance(self):
# This is assuming that instances will already be created.
return random.choice(self.instances)
if not self.instances_created:
self.create_instances()
self.instances_created = False
return random.choice(self.instances.all())
class VariableInstance(models.Model):
"""
Instance of `Variable`
"""
variable = models.ForeignKey(Variable, on_delete=models.CASCADE, related_name='instances')
value = models.FloatField(null=False, blank=False)
class VariableInterval(models.Model):
"""
A `Variable` may have multiple intervals in its domain.
For example, variable x domain may be [1,5] U [9, 17]
"""
variable = models.ForeignKey(Variable, on_delete=models.CASCADE,related_name='intervals')
lower_bound = models.FloatField(default=-999,blank=False, null=False)
upper_bound = models.FloatField(default=999, blank=False, null=False)

def __str__(self):
return f"Interval {self.lower_bound} - {self.upper_bound} for {self.variable}"
class VectorAnswer(models.Model):
# !Important: Deprecated
# A vector is simply an expression.
"""
A vector answer for a structural question can be n-dimensional. n >= 2
# TODO: !Important: the content should be an array of floats.
Expand Down

0 comments on commit d348cda

Please sign in to comment.