Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
	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
Ejemplo n.º 6
0
	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
Ejemplo n.º 7
0
	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
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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, )))
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
	def get_queryset(self) -> QuerySet[SemesterDownloadFile]:
		student = get_student_by_id(self.request, self.kwargs['pk'])
		return SemesterDownloadFile.objects.filter(semester=student.semester)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
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)