210 lines
8.7 KiB
Python
210 lines
8.7 KiB
Python
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.db.models import Q
|
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
from django.utils.html import strip_tags
|
|
from .models import Assignment, Submission, Answer
|
|
import json
|
|
from .forms import AnswersForm, StudentRegistrationForm
|
|
from django.contrib.auth import login
|
|
def register(request):
|
|
if request.user.is_authenticated:
|
|
return redirect('lms:dashboard')
|
|
if request.method == 'POST':
|
|
form = StudentRegistrationForm(request.POST)
|
|
if form.is_valid():
|
|
user = form.save(commit=False)
|
|
user.role = 'STUDENT'
|
|
user.save()
|
|
login(request, user)
|
|
return redirect('lms:dashboard')
|
|
else:
|
|
form = StudentRegistrationForm()
|
|
return render(request, 'lms/register.html', {'form': form})
|
|
|
|
def is_student(user):
|
|
return user.is_authenticated and user.role == 'STUDENT'
|
|
|
|
def home(request):
|
|
if request.user.is_authenticated:
|
|
if getattr(request.user, 'role', None) == 'STUDENT':
|
|
return redirect('lms:dashboard')
|
|
else:
|
|
return redirect('/admin/')
|
|
return render(request, 'lms/home.html')
|
|
|
|
@login_required
|
|
@user_passes_test(is_student, login_url='/login/')
|
|
def dashboard(request):
|
|
assignments = Assignment.objects.filter(
|
|
Q(assign_to_all=True) | Q(assigned_students=request.user),
|
|
is_published=True
|
|
).distinct().order_by('-created_at')
|
|
|
|
# Check which ones the student has submitted
|
|
submissions = Submission.objects.filter(student=request.user)
|
|
submitted_assignment_ids = submissions.values_list('assignment_id', flat=True)
|
|
|
|
from django.utils import timezone
|
|
return render(request, 'lms/dashboard.html', {
|
|
'assignments': assignments,
|
|
'submitted_assignment_ids': submitted_assignment_ids,
|
|
'submissions': {sub.assignment_id: sub for sub in submissions},
|
|
'now': timezone.now()
|
|
})
|
|
|
|
@login_required
|
|
@user_passes_test(is_student, login_url='/login/')
|
|
def assignment_detail(request, pk):
|
|
assignment = get_object_or_404(Assignment, pk=pk, is_published=True)
|
|
questions = assignment.questions.all()
|
|
|
|
# Try to get existing submission
|
|
submission = Submission.objects.filter(assignment=assignment, student=request.user).first()
|
|
|
|
from django.utils import timezone
|
|
import datetime
|
|
|
|
now = timezone.now()
|
|
can_submit = True
|
|
time_left_seconds = None
|
|
needs_start = False
|
|
|
|
if assignment.deadline and now > assignment.deadline:
|
|
can_submit = False
|
|
|
|
if assignment.time_limit:
|
|
if not submission:
|
|
if can_submit:
|
|
needs_start = True
|
|
can_submit = False # Can't submit until started
|
|
elif submission.status == 'IN_PROGRESS':
|
|
if submission.started_at:
|
|
elapsed = (now - submission.started_at).total_seconds()
|
|
allowed = assignment.time_limit * 60
|
|
if elapsed > allowed + 60: # 1 minute grace period for network latency
|
|
can_submit = False
|
|
submission.is_time_expired = True
|
|
submission.status = 'PENDING' # Or graded with 0
|
|
submission.save()
|
|
else:
|
|
time_left_seconds = int(allowed - elapsed)
|
|
if time_left_seconds < 0:
|
|
time_left_seconds = 0
|
|
else:
|
|
can_submit = False # Already submitted
|
|
|
|
if request.method == 'POST':
|
|
if needs_start and 'action' in request.POST and request.POST['action'] == 'start':
|
|
Submission.objects.create(
|
|
assignment=assignment,
|
|
student=request.user,
|
|
status='IN_PROGRESS',
|
|
started_at=now
|
|
)
|
|
return redirect('lms:assignment_detail', pk=pk)
|
|
|
|
if submission and submission.status != 'IN_PROGRESS':
|
|
return redirect('lms:dashboard')
|
|
|
|
if can_submit and not needs_start:
|
|
form = AnswersForm(request.POST, request.FILES, questions=questions)
|
|
if form.is_valid():
|
|
if not submission:
|
|
submission = Submission.objects.create(assignment=assignment, student=request.user)
|
|
|
|
all_auto = True
|
|
total_points = 0
|
|
max_points = 0
|
|
|
|
for q in questions:
|
|
answer_obj = Answer.objects.filter(submission=submission, question=q).first()
|
|
if not answer_obj:
|
|
answer_obj = Answer(submission=submission, question=q)
|
|
|
|
if q.question_type == 'TEXT_AUTO':
|
|
val = form.cleaned_data.get(f'answer_{q.id}', '')
|
|
answer_obj.text_answer = val
|
|
if q.correct_answer:
|
|
correct = strip_tags(q.correct_answer).strip().lower()
|
|
answer_obj.is_correct = (val.strip().lower() == correct)
|
|
answer_obj.points = 1 if answer_obj.is_correct else 0
|
|
total_points += answer_obj.points
|
|
else:
|
|
all_auto = False
|
|
max_points += 1
|
|
elif q.question_type == 'TABLE_AUTO':
|
|
rows = getattr(q, 'table_rows', 2) or 2
|
|
table_data = []
|
|
for i in range(1, rows + 1):
|
|
val1 = form.cleaned_data.get(f'answer_{q.id}_r{i}_c1', '')
|
|
val2 = form.cleaned_data.get(f'answer_{q.id}_r{i}_c2', '')
|
|
table_data.append([val1, val2])
|
|
answer_obj.text_answer = json.dumps(table_data)
|
|
if q.correct_answer:
|
|
correct_rows = q.correct_answer.split(';')
|
|
is_correct = True
|
|
if len(correct_rows) != rows:
|
|
is_correct = False
|
|
else:
|
|
for i, row in enumerate(correct_rows):
|
|
cols = row.split('|')
|
|
if len(cols) == 2:
|
|
if cols[0].strip().lower() != table_data[i][0].strip().lower() or cols[1].strip().lower() != table_data[i][1].strip().lower():
|
|
is_correct = False
|
|
break
|
|
else:
|
|
is_correct = False
|
|
break
|
|
answer_obj.is_correct = is_correct
|
|
answer_obj.points = 1 if is_correct else 0
|
|
total_points += answer_obj.points
|
|
else:
|
|
all_auto = False
|
|
max_points += 1
|
|
else:
|
|
all_auto = False
|
|
val = form.cleaned_data.get(f'file_{q.id}')
|
|
if val:
|
|
answer_obj.file_answer = val
|
|
answer_obj.save()
|
|
|
|
if all_auto and max_points > 0:
|
|
submission.status = 'GRADED'
|
|
submission.grade = f"{total_points}/{max_points}"
|
|
else:
|
|
submission.status = 'PENDING'
|
|
submission.save()
|
|
return redirect('lms:dashboard')
|
|
else:
|
|
form = None
|
|
else:
|
|
if can_submit and not needs_start:
|
|
form = AnswersForm(questions=questions)
|
|
else:
|
|
form = None
|
|
|
|
question_fields = []
|
|
if form:
|
|
for q in questions:
|
|
if q.question_type == 'TEXT_AUTO':
|
|
question_fields.append((q, 'text', form[f'answer_{q.id}']))
|
|
elif q.question_type == 'TABLE_AUTO':
|
|
rows = getattr(q, 'table_rows', 2) or 2
|
|
table_fields = []
|
|
for i in range(1, rows + 1):
|
|
table_fields.append([form[f'answer_{q.id}_r{i}_c1'], form[f'answer_{q.id}_r{i}_c2']])
|
|
question_fields.append((q, 'table', table_fields))
|
|
else:
|
|
question_fields.append((q, 'file', form[f'file_{q.id}']))
|
|
|
|
return render(request, 'lms/assignment_detail.html', {
|
|
'assignment': assignment,
|
|
'form': form,
|
|
'submission': submission,
|
|
'questions': questions,
|
|
'question_fields': question_fields,
|
|
'can_submit': can_submit,
|
|
'needs_start': needs_start,
|
|
'time_left_seconds': time_left_seconds,
|
|
})
|