Skip to content

Commit b4884be

Browse files
Merge pull request #13 from rockstarr-programmerr/dev
Dev
2 parents fbb0553 + bc2ea09 commit b4884be

File tree

10 files changed

+70
-19
lines changed

10 files changed

+70
-19
lines changed

classroom/business/all/student_report.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import timedelta
2+
13
from .band_score import BAND_SCORE_MAPPER
24

35

@@ -19,6 +21,9 @@ def make(self, exercise_pk=None, detail=True):
1921

2022
if submission:
2123
report['submitted'] = True
24+
report['time_taken'] = submission.time_taken
25+
report['submit_datetime'] = submission.submit_datetime
26+
2227
questions = exercise.questions.all().prefetch_related('answers')
2328
answers = submission.answers.all()
2429

@@ -50,6 +55,8 @@ def make(self, exercise_pk=None, detail=True):
5055
def new_report(exercise):
5156
return {
5257
'exercise': exercise,
58+
'time_taken': timedelta(seconds=0),
59+
'submit_datetime': None,
5360
'passage_1_total': 0,
5461
'passage_2_total': 0,
5562
'passage_3_total': 0,

classroom/business/teacher/classroom.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import secrets
21
import os
2+
import secrets
33

44
from django.contrib.auth import get_user_model
5-
from django.core.files.storage import default_storage
65

6+
from classroom.storages import ExerciseImageStorage
77
from classroom.tasks import send_temp_password_for_new_students_task
88

99
User = get_user_model()
@@ -75,14 +75,16 @@ def resend_password_emails(classroom, email):
7575

7676

7777
def upload_reading_exercise_image(image):
78+
storage = ExerciseImageStorage()
79+
7880
directory = 'classroom/reading_exercises/uploaded_images'
7981
path = f'{directory}/{image.name}'
8082

81-
if default_storage.exists(path):
83+
if storage.exists(path):
8284
img_name, img_ext = os.path.splitext(image.name)
8385
unique_name = img_name + secrets.token_urlsafe(nbytes=8)
8486
path = f'{directory}/{unique_name}{img_ext}'
8587

86-
default_storage.save(path, image)
87-
url = default_storage.url(path)
88+
storage.save(path, image)
89+
url = storage.url(path)
8890
return url
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 3.2.7 on 2021-10-23 14:58
2+
3+
import datetime
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('classroom', '0014_alter_readingquestion_options'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='readingsubmission',
16+
name='time_taken',
17+
field=models.DurationField(default=datetime.timedelta(0)),
18+
),
19+
]

classroom/models/reading_submission.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import timedelta
2+
13
from django.contrib.auth import get_user_model
24
from django.db import models
35

@@ -10,6 +12,7 @@ class ReadingSubmission(models.Model):
1012
exercise = models.ForeignKey(ReadingExercise, on_delete=models.CASCADE, related_name='submissions')
1113
submitter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reading_submissions')
1214
submit_datetime = models.DateTimeField(auto_now_add=True)
15+
time_taken = models.DurationField(default=timedelta(seconds=0))
1316

1417
class Meta:
1518
ordering = ['exercise', 'submitter', 'submit_datetime']

classroom/serializers/classroom.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class StudentReadingReportSerializer(serializers.Serializer):
8787
show_detail = serializers.BooleanField(write_only=True, default=True)
8888

8989
exercise = _ExerciseSerializer(read_only=True)
90+
time_taken = serializers.DurationField(read_only=True)
91+
submit_datetime = serializers.DateTimeField(read_only=True)
9092
passage_1_total = serializers.IntegerField(read_only=True)
9193
passage_2_total = serializers.IntegerField(read_only=True)
9294
passage_3_total = serializers.IntegerField(read_only=True)

classroom/serializers/exercise.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@ def save(self, **kwargs):
2727
return super().save(**kwargs)
2828

2929

30-
class _SubmitAnswerListSerializer(serializers.ListSerializer):
30+
class _ReadingExerciseAnswerSerializer(serializers.Serializer):
31+
question_number = serializers.IntegerField(min_value=1)
32+
content = serializers.CharField()
33+
34+
35+
class ReadingExerciseSubmitSerializer(serializers.Serializer):
36+
time_taken = serializers.DurationField(
37+
help_text='Number of seconds, or a string in this format: "[DD] [HH:[MM:]]ss[.uuuuuu]".'
38+
)
39+
answers = _ReadingExerciseAnswerSerializer(many=True)
40+
3141
def save(self, exercise=None):
3242
student = self.context['request'].user
3343
if exercise.submissions.filter(submitter=student).exists():
@@ -38,11 +48,16 @@ def save(self, exercise=None):
3848
self._create_submission_answers(submission)
3949

4050
def _create_submission(self, student, exercise):
41-
return ReadingSubmission.objects.create(submitter=student, exercise=exercise)
51+
time_taken = self.validated_data['time_taken']
52+
return ReadingSubmission.objects.create(
53+
submitter=student,
54+
exercise=exercise,
55+
time_taken=time_taken,
56+
)
4257

4358
def _create_submission_answers(self, submission):
4459
answers_to_create = []
45-
for answer in self.validated_data:
60+
for answer in self.validated_data['answers']:
4661
answers_to_create.append(
4762
ReadingSubmissionAnswer(
4863
submission=submission,
@@ -53,14 +68,6 @@ def _create_submission_answers(self, submission):
5368
ReadingSubmissionAnswer.objects.bulk_create(answers_to_create)
5469

5570

56-
class ReadingExerciseSubmitSerializer(serializers.Serializer):
57-
question_number = serializers.IntegerField(min_value=1)
58-
content = serializers.CharField()
59-
60-
class Meta:
61-
list_serializer_class = _SubmitAnswerListSerializer
62-
63-
6471
class ReadingExerciseUploadImgSerializer(serializers.Serializer):
6572
image = serializers.ImageField(write_only=True)
6673
image_url = serializers.URLField(read_only=True)

classroom/serializers/reading_submission.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ class Meta:
1212
model = ReadingSubmission
1313
fields = [
1414
'pk', 'url', 'exercise',
15-
'submitter', 'submit_datetime', 'answers',
15+
'submitter', 'submit_datetime',
16+
'time_taken', 'answers',
1617
]
1718
extra_kwargs = {
1819
'url': {'view_name': 'reading-submission-detail'},

classroom/storages.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from storages.backends.s3boto3 import S3StaticStorage
2+
3+
4+
class ExerciseImageStorage(S3StaticStorage):
5+
"""
6+
Because image URL is saved in html by CKEditor, we must use S3StaticStorage
7+
so that `querystring_auth` is disabled, and URL is always the same.
8+
Also, set `default_acl` to 'public-read' so that these images are accessible.
9+
"""
10+
default_acl = 'public-read'

classroom/views/exercise.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ReadingExerciseViewSet(ModelViewSet):
2727
def submit_answers(self, request, pk):
2828
"""Student submit their answer to this exercise.
2929
"""
30-
serializer = self.get_serializer(data=request.data, many=True)
30+
serializer = self.get_serializer(data=request.data)
3131
serializer.is_valid(raise_exception=True)
3232
exercise = self.get_object()
3333
serializer.save(exercise=exercise)

keep_learning/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@
235235

236236
SIMPLE_JWT = {
237237
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
238-
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
238+
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
239239
'ROTATE_REFRESH_TOKENS': True,
240240
'BLACKLIST_AFTER_ROTATION': True,
241241
'UPDATE_LAST_LOGIN': True, # Needed for invalidating used password reset token

0 commit comments

Comments
 (0)