Skip to content

Commit ef867b4

Browse files
remove from-to number, add answer
1 parent 984143e commit ef867b4

File tree

11 files changed

+170
-57
lines changed

11 files changed

+170
-57
lines changed

classroom/filters/question.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ class Meta:
88
model = ReadingQuestion
99
fields = {
1010
'exercise': ['exact'],
11+
'passage': ['exact'],
12+
'number': ['exact', 'gte', 'lte'],
1113
}
1214

1315
@property
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 3.2.7 on 2021-10-05 15:27
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('classroom', '0007_auto_20211005_2147'),
11+
]
12+
13+
operations = [
14+
migrations.AlterModelOptions(
15+
name='readingquestion',
16+
options={'ordering': ['exercise']},
17+
),
18+
migrations.RenameField(
19+
model_name='readingquestion',
20+
old_name='from_number',
21+
new_name='number',
22+
),
23+
migrations.RemoveField(
24+
model_name='readingquestion',
25+
name='to_number',
26+
),
27+
migrations.CreateModel(
28+
name='ReadingCorrectAnswer',
29+
fields=[
30+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31+
('content', models.CharField(max_length=255)),
32+
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='correct_answers', to='classroom.readingquestion')),
33+
],
34+
options={
35+
'ordering': ['content'],
36+
},
37+
),
38+
]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 3.2.7 on 2021-10-05 15:32
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('classroom', '0008_auto_20211005_2227'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='ReadingAnswer',
16+
fields=[
17+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('content', models.CharField(max_length=255)),
19+
],
20+
options={
21+
'ordering': ['content'],
22+
},
23+
),
24+
migrations.RenameField(
25+
model_name='readingquestion',
26+
old_name='answers',
27+
new_name='choices',
28+
),
29+
migrations.DeleteModel(
30+
name='ReadingCorrectAnswer',
31+
),
32+
migrations.AddField(
33+
model_name='readinganswer',
34+
name='question',
35+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='classroom.readingquestion'),
36+
),
37+
]

classroom/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .classroom import Classroom
2+
from .reading_answer import ReadingAnswer
23
from .reading_exercise import ReadingExercise
34
from .reading_question import ReadingQuestion

classroom/models/abstracts/answer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from django.db import models
2+
3+
4+
class Answer(models.Model):
5+
content = models.CharField(max_length=255)
6+
7+
class Meta:
8+
abstract = True

classroom/models/abstracts/question.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,39 @@ class Types(models.TextChoices):
99
YES_NO = 'yes_no', _('Yes / No / Not given')
1010
FILL_BLANK = 'fill_blank', _('Fill in the blank')
1111

12-
ANSWERS_DELEMITER = ' | '
12+
_DELEMITER = ' | '
1313
_TRUE = 'TRUE'
1414
_FALSE = 'FALSE'
1515
_YES = 'YES'
1616
_NO = 'NO'
1717
_NOT_GIVEN = 'NOT_GIVEN'
1818

19-
from_number = models.PositiveSmallIntegerField()
20-
to_number = models.PositiveSmallIntegerField(null=True)
19+
number = models.PositiveSmallIntegerField()
2120
question_type = models.CharField(max_length=30, choices=Types.choices)
22-
answers = models.CharField(max_length=255, blank=True)
21+
choices = models.CharField(max_length=255, blank=True)
2322
correct_answer = models.CharField(max_length=255)
2423

2524
class Meta:
2625
abstract = True
2726

2827
def __str__(self):
29-
return f'{self.get_question_number_display()} | {self.question_type} | {self.correct_answer[:50]}'
28+
return f'{self.number} | {self.get_question_type_display()} | {self.correct_answer[:50]}'
3029

3130
def save(self, *args, **kwargs):
32-
self.set_default_answers()
31+
self.set_default_choices()
3332
return super().save(*args, **kwargs)
3433

35-
def get_question_number_display(self):
36-
display = str(self.from_number)
37-
if self.to_number is not None and self.to_number != self.from_number:
38-
display += f' - {self.to_number}'
39-
return display
40-
41-
def set_default_answers(self):
34+
def set_default_choices(self):
4235
if self.is_true_false():
43-
self.answers = self.ANSWERS_DELEMITER.join([
36+
self.choices = self._DELEMITER.join([
4437
self._TRUE, self._FALSE, self._NOT_GIVEN
4538
])
4639
elif self.is_yes_no():
47-
self.answers = self.ANSWERS_DELEMITER.join([
40+
self.choices = self._DELEMITER.join([
4841
self._YES, self._NO, self._NOT_GIVEN
4942
])
5043
elif not self.is_multiple_choice():
51-
self.answers = ''
44+
self.choices = ''
5245

5346
def is_true_false(self):
5447
return self.question_type == self.Types.TRUE_FALSE
@@ -58,3 +51,9 @@ def is_yes_no(self):
5851

5952
def is_multiple_choice(self):
6053
return self.question_type == self.Types.MULTIPLE_CHOICE
54+
55+
def get_answers_content(self):
56+
answers = []
57+
for answer in self.answers.all():
58+
answers.append(answer.content)
59+
return answers

classroom/models/reading_answer.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.db import models
2+
3+
from .abstracts.answer import Answer
4+
5+
6+
class ReadingAnswer(Answer):
7+
question = models.ForeignKey('ReadingQuestion', on_delete=models.CASCADE, related_name='answers')
8+
9+
class Meta:
10+
ordering = ['content']

classroom/models/reading_exercise.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,5 @@ def __str__(self):
2323
return display
2424

2525
def get_question_range(self):
26-
question_numbers = self.questions.order_by('from_number').values('from_number', 'to_number')
27-
question_range = []
28-
for number in question_numbers:
29-
from_number = number['from_number']
30-
to_number = number['to_number']
31-
if to_number is None:
32-
question_range.append(from_number)
33-
else:
34-
question_range.extend(range(from_number, to_number + 1))
35-
return question_range
26+
question_range = self.questions.all().values_list('number', flat=True)
27+
return list(question_range)

classroom/models/reading_question.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,24 @@
22

33
from .abstracts.question import Question
44
from .reading_exercise import ReadingExercise
5+
from .reading_answer import ReadingAnswer
56

67

78
class ReadingQuestion(Question):
89
exercise = models.ForeignKey(ReadingExercise, on_delete=models.CASCADE, related_name='questions')
910
passage = models.PositiveSmallIntegerField(default=1)
1011

1112
class Meta:
12-
ordering = ['exercise', 'from_number']
13+
ordering = ['exercise']
14+
15+
def create_answers(self, answers):
16+
contents = answers.split(self._DELEMITER)
17+
answers_objs = [
18+
ReadingAnswer(question=self, content=content)
19+
for content in contents
20+
]
21+
ReadingAnswer.objects.bulk_create(answers_objs)
22+
23+
def replace_answers(self, answers):
24+
self.answers.all().delete()
25+
self.create_answers(answers)

classroom/serializers/question.py

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,68 @@
55

66

77
class ReadingQuestionSerializer(serializers.HyperlinkedModelSerializer):
8+
answers = serializers.CharField(source='get_answers_content')
9+
810
class Meta:
911
model = ReadingQuestion
1012
fields = [
1113
'pk', 'url', 'exercise', 'passage',
12-
'from_number', 'to_number', 'question_type',
13-
'answers', 'correct_answer',
14+
'number', 'question_type',
15+
'choices', 'answers',
1416
]
1517
extra_kwargs = {
1618
'url': {'view_name': 'reading-question-detail'},
1719
'exercise': {'view_name': 'reading-exercise-detail'},
1820
'passage': {'min_value': 1},
19-
'from_number': {'min_value': 1},
20-
'to_number': {'min_value': 1},
21+
'number': {'min_value': 1},
2122
}
2223

2324
def validate(self, attrs):
2425
attrs = super().validate(attrs)
25-
self._validate_from_to_number(attrs)
2626
self._validate_question_not_exist(attrs)
27+
self._handle_get_answers_content(attrs)
2728
return attrs
2829

29-
def _validate_from_to_number(self, attrs):
30-
from_number = attrs['from_number']
31-
to_number = attrs['to_number']
32-
if to_number is not None and not to_number > from_number:
33-
raise serializers.ValidationError(
34-
_('"To" must be greater than "From".')
35-
)
36-
3730
def _validate_question_not_exist(self, attrs):
38-
from_number = attrs['from_number']
39-
exercise = attrs['exercise']
31+
number = attrs.get('number')
32+
if number is None:
33+
return
34+
35+
exercise = attrs.get('exercise') or self.instance.exercise # If `attrs` don't have 'exercise',
36+
# there should be `self.instance`
4037
question_range = exercise.get_question_range()
4138

4239
if self.instance:
43-
if self.instance.to_number is not None:
44-
instance_range = range(self.instance.from_number, self.instance.to_number + 1)
45-
else:
46-
instance_range = [self.instance.from_number]
47-
for number in instance_range:
48-
try:
49-
index = question_range.index(number)
50-
question_range.pop(index)
51-
except ValueError:
52-
pass
53-
54-
if from_number in question_range:
40+
try:
41+
index = question_range.index(self.instance.number)
42+
question_range.pop(index)
43+
except ValueError:
44+
pass
45+
46+
if number in question_range:
5547
raise serializers.ValidationError(
5648
_('This question number already exists.')
5749
)
5850

59-
# TODO: validation regarding `question_type` and `correct_answer`
51+
def _handle_get_answers_content(self, attrs):
52+
if 'get_answers_content' in attrs:
53+
attrs['answers'] = attrs.pop('get_answers_content')
54+
55+
def create(self, validated_data):
56+
has_answers = 'answers' in validated_data
57+
if has_answers:
58+
answers = validated_data.pop('answers')
59+
60+
question = super().create(validated_data)
61+
62+
if has_answers:
63+
question.create_answers(answers)
64+
65+
return question
66+
67+
def update(self, question, validated_data):
68+
if 'answers' in validated_data:
69+
answers = validated_data.pop('answers')
70+
question.replace_answers(answers)
71+
72+
return super().update(question, validated_data)

0 commit comments

Comments
 (0)