def uploads(request: HttpRequest, student_id: int, unit_id: int) -> HttpResponse: student = get_student_by_id(request, student_id) unit = get_object_or_404(Unit, id=unit_id) uploads = UploadedFile.objects.filter(benefactor=student, unit=unit) if not student.check_unit_unlocked(unit) and not uploads.exists(): raise PermissionDenied("This unit is not unlocked yet") form = None if request.method == "POST": form = NewUploadForm(request.POST, request.FILES) if form.is_valid(): new_upload = form.save(commit=False) new_upload.unit = unit new_upload.benefactor = student new_upload.owner = request.user new_upload.save() messages.success(request, "New file has been uploaded.") form = None # clear form on successful upload, prevent duplicates if form is None: form = NewUploadForm(initial={'unit': unit}) context: Dict[str, Any] = {} context['title'] = 'File Uploads' context['student'] = student context['unit'] = unit context['form'] = form context['files'] = uploads # TODO form for adding new files return render(request, "dashboard/uploads.html", context)
def curriculum(request: HttpRequest, student_id: int) -> HttpResponse: student = get_student_by_id(request, student_id) units = Unit.objects.filter(group__hidden=False) original = student.curriculum.values_list('id', flat=True) enabled = can_edit(request, student) or student.newborn if request.method == 'POST' and enabled: form = CurriculumForm(request.POST, units=units, enabled=True) if form.is_valid(): data = form.cleaned_data # get groups with nonempty unit sets unit_lists = [ data[k] for k in data if k.startswith('group-') and data[k] is not None ] values = [unit for unit_list in unit_lists for unit in unit_list] student.curriculum.set(values) student.save() messages.success( request, f"Successfully saved curriculum of {len(values)} units.") else: form = CurriculumForm(units=units, original=original, enabled=enabled) if not enabled: messages.info( request, "You can't edit this curriculum " + "since you are not an instructor.") context = { 'title': "Units for " + student.name, 'student': student, 'form': form, 'enabled': enabled } return render(request, "roster/currshow.html", context)
def advance(request: HttpRequest, student_id: int) -> Any: student = get_student_by_id(request, student_id, requires_edit=True) if request.method == 'POST': form = AdvanceForm(request.POST, instance=student) if form.is_valid(): form.save() messages.success(request, "Successfully advanced student.") # uncomment the below if you want to load the portal again # return HttpResponseRedirect(reverse("portal", args=(student_id,))) else: form = AdvanceForm(instance=student) context: Dict[str, Any] = {'title': "Advance " + student.name} context['form'] = form context['student'] = student context['omniscient'] = can_edit(request, student) context['curriculum'] = student.generate_curriculum_rows( omniscient=context['omniscient']) if student.semester.uses_legacy_pset_system: uploads = student.uploadedfile_set # type: ignore context['num_psets'] = uploads.filter( category='psets').values('unit').distinct().count() else: context['num_psets'] = PSet.objects.filter(student=student).count() return render(request, "roster/advance.html", context)
def submit_pset(request: HttpRequest, student_id: int) -> HttpResponse: student = get_student_by_id(request, student_id) if student.semester.active is False: raise PermissionDenied("Not an active semester") if student.enabled is False: raise PermissionDenied("Not enabled") if student.is_delinquent: raise PermissionDenied("Student is delinquent") if request.method == 'POST': form = PSetSubmitForm(request.POST, request.FILES) else: form = PSetSubmitForm() form.fields['unit'].queryset = get_units_to_submit(student) # type: ignore form.fields['next_unit_to_unlock'].queryset = get_units_to_unlock(student) # type: ignore if request.method == 'POST' and form.is_valid(): pset = form.save(commit=False) if PSet.objects.filter(student=student, unit=pset.unit).exists(): messages.error( request, "You have already submitted for this unit. " "If this is intentional, you should use the resubmit button " "at the bottom of this page instead." ) else: f = UploadedFile( benefactor=student, owner=student.user, category='psets', description='', content=form.cleaned_data['content'], unit=pset.unit, ) f.save() pset.student = student pset.upload = f pset.save() messages.success( request, "The problem set is submitted successfully " "and is pending review!" ) logger.log( VERBOSE_LOG_LEVEL, f"{student} submitted for {pset.unit}", extra={'request': request} ) return HttpResponseRedirect(pset.get_absolute_url()) context = { 'title': 'Ready to submit?', 'student': student, 'pending_psets': PSet.objects.filter(student=student, approved=False).order_by('-upload__created_at'), 'approved_psets': PSet.objects.filter(student=student, approved=True).order_by('-upload__created_at'), 'form': form, } return render(request, "dashboard/submit_pset_form.html", context)
def get_object(self, *args: Any, **kwargs: Any) -> Achievement: student = get_student_by_id(self.request, self.kwargs['student_id']) if not student.semester.active: raise PermissionDenied("The palace can't be edited through an inactive student") assert_maxed_out_level_info(student) self.student = student achievement, _ = Achievement.objects.get_or_create(creator=student.user) return achievement
def get_object(self, *args: Any, **kwargs: Any) -> PalaceCarving: student = get_student_by_id(self.request, self.kwargs['student_id']) assert_maxed_out_level_info(student) self.student = student carving, is_created = PalaceCarving.objects.get_or_create(user=student.user) if is_created is True: carving.display_name = student.name return carving
def get_queryset(self): student = get_student_by_id(self.request, self.kwargs['student_id']) assert_maxed_out_level_info(student) self.student = student queryset = PalaceCarving.objects.filter(visible=True) queryset = queryset.exclude(display_name="") queryset = queryset.order_by('created_at') return queryset
def show_exam(request: AuthHttpRequest, student_id: int, pk: int) -> HttpResponse: context: Dict[str, Any] = {} quiz = get_object_or_404(PracticeExam, pk=pk) if quiz.is_test: return HttpResponseForbidden( "You can only use this view for short-answer quizzes.") student = get_student_by_id(request, student_id) if student.semester.exam_family != quiz.family: return HttpResponseForbidden("Wrong year of practice exams") elif not student.enabled: return HttpResponseForbidden("Student account not enabled") return render(request, 'exams/quiz_detail.html', context)
def finalize(request: HttpRequest, student_id: int) -> HttpResponse: # Removes a newborn status, thus activating everything student = get_student_by_id(request, student_id) if student.curriculum.count() > 0: student.newborn = False first_units = student.curriculum.all()[0:3] student.unlocked_units.set(first_units) student.save() messages.success( request, "Your curriculum has been finalized! " "You can start working now; " "the first three units have been unlocked.") else: messages.error( request, "You didn't select any units. " "You should select some units before using this link.") return HttpResponseRedirect(reverse("portal", args=(student_id, )))
def stats(request: AuthHttpRequest, student_id: int) -> HttpResponse: student = get_student_by_id(request, student_id) unlocks = AchievementUnlock.objects.filter(user=student.user) unlocks = unlocks.select_related('achievement').order_by('-timestamp') context: Dict[str, Any] = { 'student': student, 'form': DiamondsForm(), 'achievements': unlocks, } if request.method == 'POST': assert student.user is not None form = DiamondsForm(request.POST) if form.is_valid(): code = form.cleaned_data['code'] if AchievementUnlock.objects.filter(user=student.user, achievement__code=code).exists(): messages.warning(request, "You already earned this achievement!") else: try: achievement = Achievement.objects.get(code__iexact=code) except Achievement.DoesNotExist: messages.error(request, "You entered an invalid code.") logger.warn(f"Invalid diamond code `{code}`", extra={'request': request}) else: logger.log( SUCCESS_LOG_LEVEL, f"{student.name} obtained {achievement}", extra={'request': request} ) AchievementUnlock.objects.create(user=student.user, achievement=achievement) context['obtained_achievement'] = achievement form = DiamondsForm() else: form = DiamondsForm() try: context['first_achievement'] = Achievement.objects.get(pk=1) except Achievement.DoesNotExist: pass level_info = get_level_info(student) context.update(level_info) level_number = level_info['level_number'] obtained_levels = Level.objects.filter(threshold__lte=level_number).order_by('-threshold') context['obtained_levels'] = obtained_levels return render(request, "dashboard/stats.html", context)
def portal(request: AuthHttpRequest, student_id: int) -> HttpResponse: student = get_student_by_id(request, student_id, payment_exempt=True) if not request.user.is_staff and student.is_delinquent: return HttpResponseRedirect(reverse_lazy('invoice', args=(student_id, ))) semester = student.semester profile, _ = UserProfile.objects.get_or_create(user=request.user) student_profile, _ = UserProfile.objects.get_or_create(user=student.user) level_info = get_level_info(student) if request.user == student.user: result = check_level_up(student) if result is True and profile.show_bars is True: lvl = level_info['level_number'] messages.success(request, f"You leveled up! You're now level {lvl}.") context: Dict[str, Any] = {} context['title'] = f"{student.name} ({semester.name})" context['last_seen'] = student_profile.last_seen context['student'] = student context['semester'] = semester context['profile'] = profile context['omniscient'] = can_edit(request, student) context['curriculum'] = student.generate_curriculum_rows(omniscient=context['omniscient']) context['tests'] = PracticeExam.objects.filter( is_test=True, family=semester.exam_family, due_date__isnull=False ) context['quizzes'] = PracticeExam.objects.filter( is_test=False, family=semester.exam_family, due_date__isnull=False ) context['emails'] = [ e for e in get_mailchimp_campaigns(14) if e['timestamp'] >= profile.last_email_dismiss ] context['downloads'] = SemesterDownloadFile.objects.filter( semester=semester, created_at__gte=profile.last_download_dismiss, ).filter( created_at__gte=timezone.now() - timedelta(days=14), ) context['markets'] = Market.active.all() context['num_sem_downloads'] = SemesterDownloadFile.objects.filter(semester=semester).count() context.update(level_info) return render(request, "dashboard/portal.html", context)
def invoice(request: HttpRequest, student_id: int = None) -> HttpResponse: if student_id is None: student = infer_student(request) return HttpResponseRedirect(reverse("invoice", args=(student.id, ))) # Now assume student_id is not None student = get_student_by_id(request, student_id, payment_exempt=True) try: invoice: Optional[Invoice] = student.invoice except ObjectDoesNotExist: invoice = None context = { 'title': "Invoice for " + student.name, 'student': student, 'invoice': invoice, 'checksum': student.get_checksum(settings.INVOICE_HASH_KEY) } # return HttpResponse("hi") return render(request, "roster/invoice.html", context)
def mocks(request: AuthHttpRequest, student_id: int = None) -> HttpResponse: if student_id is None: student = infer_student(request) return HttpResponseRedirect(reverse("mocks", args=(student.id, ))) student = get_student_by_id(request, student_id) semester = student.semester if not semester.active: return HttpResponseForbidden("Semester not active") elif not student.enabled: return HttpResponseForbidden("Student account not enabled") context = { 'student': student, 'semester': semester, 'tests': PracticeExam.objects.filter( family=semester.exam_family, is_test=True, ), } return render(request, 'exams/mocks.html', context)
def get_queryset(self) -> QuerySet[SemesterDownloadFile]: student = get_student_by_id(self.request, self.kwargs['pk']) return SemesterDownloadFile.objects.filter(semester=student.semester)
def quiz(request: AuthHttpRequest, student_id: int, pk: int) -> HttpResponse: student = get_student_by_id(request, student_id) context: Dict[str, Any] = {} quiz = get_object_or_404(PracticeExam, pk=pk) if quiz.is_test: return HttpResponseForbidden( "You can't submit numerical answers to an olympiad exam.") if not quiz.started: return HttpResponseForbidden("You can't start this quiz") if not student.enabled: return HttpResponseForbidden("Your student account is disabled.") if student.semester.exam_family != quiz.family: return HttpResponseForbidden("You can't access this quiz.") attempt: Optional[ExamAttempt] = None try: attempt = ExamAttempt.objects.get(student=student, quiz=pk) except ExamAttempt.DoesNotExist: if request.method == 'POST': if quiz.overdue: return HttpResponseForbidden( "You can't submit this quiz since the deadline passed.") form = ExamAttemptForm(request.POST) if form.is_valid(): attempt = form.save(commit=False) assert attempt is not None attempt.quiz = quiz attempt.student = student else: form = ExamAttemptForm() context['form'] = form else: if request.method == 'POST': return HttpResponseForbidden('You already submitted this quiz') if attempt is not None: context['attempt'] = attempt dummy_form = ExamAttemptForm(instance=attempt) for i in range(1, 6): dummy_form.fields[f'guess{i}'].disabled = True context['rows'] = [] score = 0 for i in range(1, 6): field = dummy_form.visible_fields()[i - 1] guess_str = getattr(attempt, f'guess{i}') guess_val = expr_compute(guess_str) accepted_str = getattr(quiz, f'answer{i}') accepted_vals = [ expr_compute(_) for _ in accepted_str.split(',') if _ ] if guess_val is not None: correct = any(v is not None and abs(guess_val - v) < 1e-12 for v in accepted_vals) else: correct = False context['rows'].append({ 'field': field, 'accepted': accepted_str, 'correct': correct, 'url': getattr(quiz, f'url{i}', None) }) if correct: score += 1 if attempt.score != score: attempt.score = score attempt.save() context['quiz'] = quiz context['student'] = student return render(request, 'exams/quiz.html', context)
def inquiry(request: AuthHttpRequest, student_id: int) -> HttpResponse: student = get_student_by_id(request, student_id) if not student.semester.active: raise PermissionDenied( "Not an active semester, so change petitions are no longer possible." ) if student.is_delinquent: raise PermissionDenied("Student is delinquent") if not student.enabled: raise PermissionDenied("Student account not enabled") if student.newborn: raise PermissionDenied( "This form isn't enabled yet because you have not chosen your initial units." ) context: Dict[str, Any] = {} current_inquiries = UnitInquiry.objects.filter(student=student) # Create form for submitting new inquiries if request.method == 'POST': form = InquiryForm(request.POST, student=student) if form.is_valid(): inquiry = form.save(commit=False) inquiry.student = student # check if exists already and created recently if UnitInquiry.objects.filter( unit=inquiry.unit, student=student, action_type=inquiry.action_type, created_at__gte=timezone.now() - datetime.timedelta(seconds=90), status="NEW", ).exists(): messages.warning( request, "The same petition already was " "submitted within the last 90 seconds.") else: inquiry.save() num_past_unlock_inquiries = current_inquiries.filter( action_type="UNLOCK").count() unlocked_count = current_inquiries.filter( action_type="UNLOCK", status="NEW").count() + student.unlocked_units.count() # auto reject criteria auto_reject_criteria = inquiry.action_type == "UNLOCK" and unlocked_count > 9 # auto hold criteria num_psets = PSet.objects.filter(student=student).count() auto_hold_criteria = (num_past_unlock_inquiries > (10 + 1.5 * num_psets)) # auto-acceptance criteria auto_accept_criteria = (inquiry.action_type == "APPEND") auto_accept_criteria |= (num_past_unlock_inquiries <= 6 and unlocked_count < 9 and (not auto_hold_criteria and not auto_reject_criteria)) if auto_reject_criteria and not request.user.is_staff: inquiry.status = "REJ" inquiry.save() messages.error( request, "You can't have more than 9 unfinished units unlocked at once." ) elif auto_accept_criteria or request.user.is_staff: inquiry.run_accept() messages.success(request, "Petition automatically approved.") elif auto_hold_criteria: inquiry.status = "HOLD" inquiry.save() messages.warning( request, "You have submitted an abnormally large number of petitions " + "so you should contact Evan specially to explain why.") else: messages.success(request, "Petition submitted, wait for it!") else: form = InquiryForm(student=student) context['form'] = form context['inquiries'] = UnitInquiry.objects.filter(student=student) context['student'] = student context['curriculum'] = student.generate_curriculum_rows( omniscient=can_edit(request, student)) return render(request, 'roster/inquiry.html', context)