Skip to content

Commit

Permalink
Add "Enrolees selection" menu with enrollment entry reasons for every…
Browse files Browse the repository at this point in the history
… course (#926)

* Done, untested

* Done
  • Loading branch information
Dmi4er4 authored Dec 25, 2024
1 parent ebb92f8 commit b5c793d
Show file tree
Hide file tree
Showing 21 changed files with 469 additions and 125 deletions.
2 changes: 1 addition & 1 deletion apps/admission/locale/ru/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-23 11:52+0000\n"
"POT-Creation-Date: 2024-12-23 15:35+0000\n"
"PO-Revision-Date: 2024-07-23 17:04+0000\n"
"Last-Translator: Дмитрий Чернушевич <[email protected]>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down
3 changes: 3 additions & 0 deletions apps/courses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,9 @@ def get_gradebook_url(self, url_name: str = "teaching:gradebook",
if student_group is not None:
url += f'?student_group={student_group}'
return url

def get_enrolees_selection_url(self):
return reverse("staff:enrolees_selection_csv", kwargs=self.url_kwargs)

def get_course_news_notifications_url(self):
return reverse('course_news_notifications_read', kwargs=self.url_kwargs,
Expand Down
2 changes: 1 addition & 1 deletion apps/htmlpages/locale/ru/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-23 11:52+0000\n"
"POT-Creation-Date: 2024-12-23 15:35+0000\n"
"PO-Revision-Date: 2015-03-18 08:34+0000\n"
"Last-Translator: Jannis Leidel <[email protected]>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/django/language/"
Expand Down
2 changes: 1 addition & 1 deletion apps/learning/gradebook/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def get(self, request, *args, **kwargs):
_("Branch"),
_("Role"),
_("Email"),
"Тип записи",
_("Enrollment type"),
_("CSCUser|Curriculum year"),
_("Group"),
_("Yandex Login"),
Expand Down
5 changes: 3 additions & 2 deletions apps/learning/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from django.conf import settings
from django.db.models import Case, Count, F, IntegerField, Prefetch, Q, When
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponse, FileResponse

from admission.models import Applicant
Expand Down Expand Up @@ -808,7 +809,7 @@ def durability_prefix(meta_course_id):
"Имя",
"Отчество",
"Тип",
"Профиль на сайте",
_("User url"),
"Пол",
"Дата рождения",
"Почта",
Expand Down Expand Up @@ -1032,7 +1033,7 @@ def _generate_headers(
"Фамилия",
"Имя",
"Отчество",
"Профиль на сайте",
_("User url"),
"Почта",
"Телефон",
"Работа",
Expand Down
2 changes: 1 addition & 1 deletion apps/learning/teaching/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def test_view_assignment_student_answers_csv(client):
client.login(teacher)

headers = [
"Профиль на сайте",
"User url",
"id",
"Фамилия",
"Имя",
Expand Down
3 changes: 2 additions & 1 deletion apps/learning/teaching/views/assignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from django.shortcuts import get_object_or_404, redirect
from django.views import generic
from django.views.generic.edit import BaseUpdateView
from django.utils.translation import gettext_lazy as _

from auth.mixins import PermissionRequiredMixin
from core import comment_persistence
Expand Down Expand Up @@ -340,7 +341,7 @@ def get(self, request, *args, **kwargs):
response['Content-Disposition'] = f'attachment; filename="{filename}"'
writer = csv.writer(response)
headers = [
"Профиль на сайте",
_("User url"),
"id",
"Фамилия",
"Имя",
Expand Down
2 changes: 1 addition & 1 deletion apps/projects/locale/ru/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-23 11:52+0000\n"
"POT-Creation-Date: 2024-12-23 15:35+0000\n"
"PO-Revision-Date: 2022-02-21 15:24+0000\n"
"Last-Translator: Сергей Жеревчук <[email protected]>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down
56 changes: 56 additions & 0 deletions apps/staff/templates/staff/enrolees_selection_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{% extends "base.html" %}

{% load i18n %}
{% load call_method from core_tags %}

{% block content %}
<div class="container">
{% if semester_list %}
<div class="row">
<div class="col-xs-12">
{% for autumn_spring in semester_list %}

<table class="table table-stripped">
<tr>
{% for semester in autumn_spring %}
<th width="50%">{% if semester %}{{ semester|title }}{% endif %}</th>
{% endfor %}
</tr>
<tr>
{% for semester in autumn_spring %}
<td class="semester-course-list">
<ul>
{% for branch_name, courses in semester.course_offerings.items %}
<li style="list-style: none"><b>{{ branch_name }}</b></li>
{% for course in courses %}
<li>{{ course.meta_course.name }}
{% if course.duration == CourseDurations.FIRST_HALF %}
<i class="fa fa-adjust fa-rotate-180" aria-hidden="true"></i>
{% endif %}
{% if course.duration == CourseDurations.SECOND_HALF %}
<i class="fa fa-adjust" aria-hidden="true"></i>
{% endif %}
<a href="{% call_method course 'get_enrolees_selection_url' %}">CSV <i class="fa fa-download"></i></a>
</li>
{% endfor %}
{% endfor %}
</ul>
</td>
{% endfor %}
</tr>
</table>
{% endfor %}
</div>
</div>
{% else %}
<div class="row">
<div class="col-xs-3"></div>
<div class="col-xs-6 center-aligned">
<h4>{% trans "No courses yet" %}</h4>
</div>
<div class="col-xs-3">
</div>
</div>
{% endif %}
</div>
{% endblock content %}
144 changes: 144 additions & 0 deletions apps/staff/tests/test_enrolees_selection_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import csv
import io
import re
from bs4 import BeautifulSoup
import pytest
from django.conf import settings
from django.contrib.messages import get_messages

from core.models import Branch
from core.tests.factories import BranchFactory
from core.urls import reverse
from courses.tests.factories import CourseFactory, SemesterFactory
from learning.settings import StudentStatuses
from learning.tests.factories import EnrollmentFactory
from staff.tests.factories import StudentStatusLogFactory, StudentAcademicDisciplineLogFactory
from study_programs.tests.factories import AcademicDisciplineFactory
from users.tests.factories import CuratorFactory, StudentFactory, UserFactory

@pytest.mark.django_db
def test_enrolees_selection_views_security(client):
student = StudentFactory()
curator = CuratorFactory()
course = CourseFactory(semester=SemesterFactory.create_current())
urls = (reverse("staff:enrolees_selection_list"), course.get_enrolees_selection_url())
client.login(student)
for url in urls:
response = client.get(url)
assert response.status_code == 302
assert response.url.startswith("/login/?next=/staff/enrolees-selection/")
client.login(curator)
for url in urls:
response = client.get(url)
assert response.status_code == 200

@pytest.mark.django_db
def test_enrolees_selection_list_view(client):
curator = CuratorFactory()
current_semester = SemesterFactory.create_current()
course = CourseFactory(semester=current_semester)
url = reverse("staff:enrolees_selection_list")
client.login(curator)
response = client.get(url)
assert response.status_code == 200
html = BeautifulSoup(response.content, "html.parser")
next_semester = SemesterFactory.create_next(current_semester)
assert html.find(text=str(current_semester).capitalize()) is not None
assert html.find(text=str(next_semester).capitalize()) is not None
assert html.find(text=course.main_branch.name) is not None
# enrolees_selection_list.html does not has jinja templates, so there is no ability to get rid of external spaces after the course name
# Have to use re to find substring as html.find returns the string that exactly match given string
assert html.find(text=re.compile(str(course.meta_course))) is not None
next_course = CourseFactory(semester=next_semester)
response = client.get(url)
html = BeautifulSoup(response.content, "html.parser")
assert html.find(text=str(current_semester).capitalize()) is not None
assert html.find(text=str(next_semester).capitalize()) is not None
assert html.find(text=course.main_branch.name) is not None
assert html.find(text=next_course.main_branch.name) is not None
assert html.find(text=re.compile(str(course.meta_course))) is not None
assert html.find(text=re.compile(str(next_course.meta_course))) is not None

@pytest.mark.django_db
def test_enrolees_selection_csv_view(client):
curator = CuratorFactory()
student_one, student_two = StudentFactory.create_batch(2)
course = CourseFactory()
csv_download_url = course.get_enrolees_selection_url()
client.login(curator)

headers = [
"User url",
"Last name",
"First name",
"Patronymic",
"Branch",
"Student type",
"Curriculum year",
"Enrollment type",
"Entry reason"
]
answers_csv = client.get(csv_download_url).content.decode('utf-8')
data = [s for s in csv.reader(io.StringIO(answers_csv)) if s]
assert data == [headers]


enrollment_one = EnrollmentFactory(course=course, student=student_one)
enrollment_two = EnrollmentFactory(course=course, student=student_two)
student_profile_one = enrollment_one.student_profile
student_profile_one.year_of_curriculum = student_profile_one.year_of_admission
student_profile_one.save()
student_profile_two = enrollment_two.student_profile
student_profile_two.year_of_curriculum = student_profile_two.year_of_admission
student_profile_two.save()

student_one_row = [
student_one.get_absolute_url(),
student_one.last_name,
student_one.first_name,
student_one.patronymic,
student_profile_one.branch.name,
student_profile_one.get_type_display(),
str(student_profile_one.year_of_curriculum),
enrollment_one.get_type_display(),
""
]

student_two_row = [
student_two.get_absolute_url(),
student_two.last_name,
student_two.first_name,
student_two.patronymic,
student_profile_two.branch.name,
student_profile_two.get_type_display(),
str(student_profile_two.year_of_curriculum),
enrollment_two.get_type_display(),
""
]

status_log_csv = client.get(csv_download_url).content.decode('utf-8')
data = [s for s in csv.reader(io.StringIO(status_log_csv)) if s]
assert len(data) == 3
assert data[1] == student_two_row
assert data[2] == student_one_row

enrollment_one.reason_entry = "reason one"
enrollment_one.save()
enrollment_two.reason_entry = "reason two"
enrollment_two.save()
student_one.patronymic = ""
student_one.save()
branch = BranchFactory()
student_profile_two.branch = branch
student_profile_two.save()

student_one_row[-1] = "reason one"
student_one_row[3] = ""
student_two_row[-1] = "reason two"
student_two_row[4] = branch.name

status_log_csv = client.get(csv_download_url).content.decode('utf-8')
data = [s for s in csv.reader(io.StringIO(status_log_csv)) if s]
assert len(data) == 3
assert data[1] == student_two_row
assert data[2] == student_one_row
14 changes: 10 additions & 4 deletions apps/staff/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
ImportCourseGradesByEnrollmentIDView, ImportCourseGradesByYandexLoginView, ImportCourseGradesByStepikIDView
)
from staff.api.views import CreateAlumniProfiles, StudentSearchJSONView
from staff.views import (
from staff.views.views import (
AdmissionApplicantsCampaignReportView, AdmissionExamReportView,
AdmissionInterviewsReportView, CourseParticipantsIntersectionView,
EnrollmentInvitationListView, ExportsView, FutureGraduateDiplomasCSVView,
Expand All @@ -19,8 +19,8 @@
SurveySubmissionsStatsView, WillGraduateStatsReportView, AdmissionApplicantsYearReportView,
StudentAcademicDisciplineLogListView, StudentStatusLogListView, badge_number_from_csv_view, merge_users_view
)

from staff.views import autograde_projects, autofail_ungraded, create_alumni_profiles
from staff.views.views import autograde_projects, autofail_ungraded, create_alumni_profiles
from staff.views.enrolees_selection import EnroleesSelectionCSVView, EnroleesSelectionListView

app_name = 'staff'

Expand Down Expand Up @@ -114,7 +114,13 @@ def to_url(self, value):

path('api/staff/', include(([
path('alumni-profiles/', CreateAlumniProfiles.as_view(), name='create_alumni_profiles'),
], 'api')))
], 'api'))),
path('enrolees-selection/', include([
path('', EnroleesSelectionListView.as_view(), name='enrolees_selection_list'),
re_path(RE_COURSE_URI, include([
path('csv/', EnroleesSelectionCSVView.as_view(), name='enrolees_selection_csv'),
]))
])),
]


Loading

0 comments on commit b5c793d

Please sign in to comment.