Skip to content

Commit

Permalink
Refactor CourseClass and teachers list in CourseDetail (#869)
Browse files Browse the repository at this point in the history
* 1 Part

* Completed

* Tests
  • Loading branch information
Dmi4er4 authored Aug 29, 2024
1 parent 0d37fe4 commit f4830e8
Show file tree
Hide file tree
Showing 20 changed files with 451 additions and 294 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-08-21 13:32+0000\n"
"POT-Creation-Date: 2024-08-27 12:24+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
8 changes: 5 additions & 3 deletions apps/admission/management/commands/stage4_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ class Command(
Generates emails with final decision based on applicant status.
Example:
./manage.py stage4_results --branch=nsk --template-pattern="csc-admission-{year}-{branch_code}-results-{
status}" -f="status__in=['accept']"
./manage.py stage4_results --branch=mnk
--template-pattern=shad-admission-2024-results-accept
-f="status='accept'"
--invite accept
"""

def add_arguments(self, parser):
Expand Down Expand Up @@ -80,7 +82,7 @@ def handle(self, *args, **options):
campaign_id=campaign.pk
)
self.stdout.write(f"Applicants: {len(applicants)}")
statuses = applicants.values_list("status", flat=True).distinct()
statuses = applicants.exclude(status__isnull=True).values_list("status", flat=True).distinct()
for applicant in applicants:
print(applicant.email)
self.stdout.write(f"Participants with final statuses were found:")
Expand Down
2 changes: 2 additions & 0 deletions apps/courses/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class SemesterTypes(DjangoChoices):
class ClassTypes(DjangoChoices):
LECTURE = C('lecture', _("Lecture"))
SEMINAR = C('seminar', _("Seminar"))
LECTURE_AND_SEMINAR = C('lecture_and_seminar', _("Lecture and seminar"))
INVITED_LECTURE = C('invited_lecture', _("Invited lecture"))


class TeacherRoles(DjangoChoices):
Expand Down
17 changes: 4 additions & 13 deletions apps/courses/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,26 +166,18 @@ class CourseClassForm(forms.ModelForm):
choices=ClassTypes.choices)
name = forms.CharField(
label=_("CourseClass|Name"),
help_text=_('Do not use words "Lecture", "Lesson", "Seminar". Fill in only name of the theme'),
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
description = forms.CharField(
label=_("Description"),
required=False,
help_text=LATEX_MARKDOWN_HTML_ENABLED,
widget=UbereditorWidget(attrs={'autofocus': 'autofocus'}))
slides = forms.FileField(
label=_("Slides"),
required=False,
widget=forms.ClearableFileInput)
attachments = forms.FileField(
label=_("Attached files"),
label=_("Materials (presentations, instructions, reminders)"),
required=False,
help_text=_("You can select multiple files"),
widget=forms.ClearableFileInput(attrs={'multiple': 'multiple'}))
other_materials = forms.CharField(
label=_("CourseClass|Other materials"),
required=False,
help_text=LATEX_MARKDOWN_HTML_ENABLED,
widget=UbereditorWidget)
date = forms.DateField(
label=_("Date"),
help_text=_("Format: dd.mm.yyyy"),
Expand All @@ -210,9 +202,8 @@ class CourseClassForm(forms.ModelForm):

class Meta:
model = CourseClass
fields = ['venue', 'type', 'date', 'starts_at', 'ends_at', 'time_zone', 'name',
'description', 'slides', 'attachments', 'video_url',
'other_materials', 'materials_visibility', 'restricted_to']
fields = ['venue', 'type', 'translation_link', 'date', 'starts_at', 'ends_at', 'time_zone', 'name',
'description', 'attachments', 'recording_link', 'materials_visibility', 'restricted_to']

def __init__(self, locale='en', **kwargs):
course = kwargs.pop('course', None)
Expand Down
18 changes: 18 additions & 0 deletions apps/courses/migrations/0048_course_hours.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.18 on 2024-08-19 12:09

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('courses', '0047_auto_20240526_1806'),
]

operations = [
migrations.AddField(
model_name='course',
name='hours',
field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='CourseOffering|hours'),
),
]
38 changes: 38 additions & 0 deletions apps/courses/migrations/0049_auto_20240827_1221.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 3.2.18 on 2024-08-27 12:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('courses', '0048_course_hours'),
]

operations = [
migrations.AddField(
model_name='course',
name='translation_link',
field=models.URLField(blank=True, verbose_name='Translation link'),
),
migrations.AddField(
model_name='courseclass',
name='recording_link',
field=models.URLField(blank=True, verbose_name='Recording link'),
),
migrations.AddField(
model_name='courseclass',
name='translation_link',
field=models.URLField(blank=True, verbose_name='Translation link'),
),
migrations.AlterField(
model_name='course',
name='default_grade',
field=models.CharField(choices=[('without_grade', 'Without Grade'), ('not_graded', 'Not graded')], default='not_graded', max_length=100, verbose_name='Enrollment|default_grade'),
),
migrations.AlterField(
model_name='courseclass',
name='type',
field=models.CharField(choices=[('lecture', 'Lecture'), ('seminar', 'Seminar'), ('lecture_and_seminar', 'Lecture and seminar'), ('invited_lecture', 'Invited lecture')], max_length=100, verbose_name='Type'),
),
]
19 changes: 19 additions & 0 deletions apps/courses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ class Course(TimezoneAwareMixin, TimeStampedModel, DerivableFieldsMixin):
verbose_name=_("CourseOffering|capacity"),
default=0,
help_text=_("0 - unlimited"))
hours = models.PositiveSmallIntegerField(
verbose_name=_("CourseOffering|hours"),
blank=True,
null=True)
starts_on = models.DateField(
_("Starts on"),
blank=True,
Expand Down Expand Up @@ -368,6 +372,10 @@ class Course(TimezoneAwareMixin, TimeStampedModel, DerivableFieldsMixin):
help_text=_("Default visibility for class materials."),
choices=MaterialVisibilityTypes.choices,
default=MaterialVisibilityTypes.COURSE_PARTICIPANTS)
translation_link = models.URLField(
verbose_name=_("Translation link"),
blank=True
)
# TODO: recalculate on deleting course class
public_videos_count = models.PositiveIntegerField(default=0, editable=False)
public_slides_count = models.PositiveIntegerField(
Expand Down Expand Up @@ -893,6 +901,14 @@ class CourseClass(TimezoneAwareMixin, TimeStampedModel):
blank=True,
help_text=_("Both YouTube and Yandex Video are supported"),
max_length=512)
translation_link = models.URLField(
verbose_name=_("Translation link"),
blank=True
)
recording_link = models.URLField(
verbose_name=_("Recording link"),
blank=True
)
other_materials = models.TextField(
_("CourseClass|Other materials"),
blank=True,
Expand Down Expand Up @@ -1045,6 +1061,9 @@ def get_available_materials(self):
if self.other_materials:
m = ClassMaterial(type='other_materials', name=_("other"))
materials.append(m)
if self.recording_link:
m = ClassMaterial(type='recording_link', name=_("recording"))
materials.append(m)
return materials


Expand Down
1 change: 1 addition & 0 deletions apps/courses/tests/test_course_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ def test_course_class_form_available(client, curator, settings):
"date": next_day.strftime(date_format),
"starts_at": "17:20",
"ends_at": "18:50",
"recording_link": "https://test-site.com",
"time_zone": course.main_branch.get_timezone(),
"materials_visibility": MaterialVisibilityTypes.PUBLIC
}
Expand Down
26 changes: 18 additions & 8 deletions apps/courses/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,13 @@ def test_update_derivable_fields(curator, client, mocker):
assert not co.public_slides_count
assert not co.public_attachments_count
slides_file = SimpleUploadedFile("slides.pdf", b"slides_content")
client.login(curator)
form = model_to_dict(cc1)
form['slides'] = slides_file
client.post(cc1.get_update_url(), form)
CourseClassFactory(course=co, slides=slides_file,
materials_visibility=MaterialVisibilityTypes.PUBLIC)
co.refresh_from_db()
assert not co.public_videos_count
assert co.public_slides_count == 1
assert not co.public_attachments_count
cc2 = CourseClassFactory(course=co, video_url="youtuuube",
CourseClassFactory(course=co, video_url="youtuuube",
materials_visibility=MaterialVisibilityTypes.PUBLIC)
co.refresh_from_db()
assert co.public_videos_count == 1
Expand Down Expand Up @@ -215,26 +213,38 @@ def test_view_course_detail_teacher_contacts_visibility(client):
lecturer_contacts = "Lecturer contacts"
organizer_contacts = "Organizer contacts"
spectator_contacts = "Spectator contacts"
seminar_contacts = "Seminar contacts"
reviewer_contacts = "Reviewer contacts"
lecturer = TeacherFactory(private_contacts=lecturer_contacts)
organizer = TeacherFactory(private_contacts=organizer_contacts)
spectator = TeacherFactory(private_contacts=spectator_contacts)
seminar = TeacherFactory(private_contacts=spectator_contacts)
reviewer = TeacherFactory(private_contacts=spectator_contacts)
course = CourseFactory()
ct_lec = CourseTeacherFactory(course=course, teacher=lecturer,
roles=CourseTeacher.roles.lecturer)
ct_org = CourseTeacherFactory(course=course, teacher=organizer,
roles=CourseTeacher.roles.organizer)
ct_spe = CourseTeacherFactory(course=course, teacher=spectator,
ct_spec = CourseTeacherFactory(course=course, teacher=spectator,
roles=CourseTeacher.roles.spectator)
ct_sem_org = CourseTeacherFactory(course=course, teacher=seminar,
roles=CourseTeacher.roles.seminar | CourseTeacher.roles.organizer)
сt_rev = CourseTeacherFactory(course=course, teacher=reviewer,
roles=CourseTeacher.roles.reviewer)

url = course.get_absolute_url()
client.login(lecturer)
response = client.get(url)

context_teachers = response.context_data['teachers']
assert set(context_teachers['main']) == {ct_lec, ct_org}
assert not context_teachers['others']
assert context_teachers['lecturer'] == [ct_lec]
assert context_teachers['organizer'] == [ct_org]
assert context_teachers['reviewer'] == [сt_rev]
assert context_teachers['seminar'] == [ct_sem_org]
assert smart_bytes(lecturer.get_full_name()) in response.content
assert smart_bytes(organizer.get_full_name()) in response.content
assert smart_bytes(seminar.get_full_name()) in response.content
assert smart_bytes(reviewer.get_full_name()) in response.content
assert smart_bytes(spectator.get_full_name()) not in response.content


Expand Down
12 changes: 1 addition & 11 deletions apps/courses/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,6 @@ def get_context_data(self, *args, **kwargs):
raise Redirect(to=redirect_to_login(self.request.get_full_path()))
# Teachers
by_role = group_teachers(course.course_teachers.all())
teachers = {'main': [], 'spectators': [], 'others': []}
has_organizers = False
for role, ts in by_role.items():
if role in (TeacherRoles.LECTURER, TeacherRoles.SEMINAR, TeacherRoles.ORGANIZER):
if role == TeacherRoles.ORGANIZER:
has_organizers = True
teachers['main'].extend(ts)
elif role != TeacherRoles.SPECTATOR:
teachers['others'].extend(ts)
user = self.request.user
role = course_access_role(course=course, user=user)
can_add_assignment = user.has_perm(CreateAssignment.name, course)
Expand Down Expand Up @@ -99,8 +90,7 @@ def get_context_data(self, *args, **kwargs):
'get_student_groups_url': get_student_groups_url,
'course': course,
'course_tabs': tab_list,
'has_organizers': has_organizers,
'teachers': teachers,
'teachers': by_role,
'has_access_to_private_materials': can_view_private_materials(role),
'course_invitation': course_invitation,
**self._get_additional_context(course)
Expand Down
1 change: 1 addition & 0 deletions apps/courses/views/course_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def get_initial(self, **kwargs):
course = kwargs["course"]
initial = {
"materials_visibility": course.materials_visibility,
"translation_link": course.translation_link,
"time_zone": course.main_branch.get_timezone() or None
}
# TODO: Add tests for initial data after discussion
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-08-21 13:32+0000\n"
"POT-Creation-Date: 2024-08-27 12:25+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
7 changes: 1 addition & 6 deletions apps/learning/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,12 +794,7 @@ def cs_club_prefix(meta_course_id):
return "[CS клуб] " if meta_course_is_club[meta_course_id] else ""

def durability_prefix(meta_course_id):
if meta_course_duration[meta_course_id] == CourseDurations.FIRST_HALF:
return "[В первой половине семестра]"
elif meta_course_duration[meta_course_id] == CourseDurations.SECOND_HALF:
return "[Во второй половине семестра]"
else:
return ""
return "[Полусеместровый] " if meta_course_duration[meta_course_id] != CourseDurations.FULL else ""

course_headers = (
f"{durability_prefix(course.id)}{cs_club_prefix(course.id)}{course.name}"
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-08-21 13:32+0000\n"
"POT-Creation-Date: 2024-08-27 12:25+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
2 changes: 1 addition & 1 deletion apps/surveys/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-08-21 13:32+0000\n"
"POT-Creation-Date: 2024-08-27 12:25+0000\n"
"PO-Revision-Date: 2019-10-31 16:30+0000\n"
"Last-Translator: b' <[email protected]>'\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down
2 changes: 1 addition & 1 deletion compscicenter_ru/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-08-21 13:32+0000\n"
"POT-Creation-Date: 2024-08-27 12:25+0000\n"
"PO-Revision-Date: 2020-02-03 16:52+0000\n"
"Last-Translator: b' <[email protected]>'\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down
2 changes: 1 addition & 1 deletion compsciclub_ru/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-08-21 13:32+0000\n"
"POT-Creation-Date: 2024-08-27 12:25+0000\n"
"PO-Revision-Date: 2020-09-09 04:43+0000\n"
"Last-Translator: b' <[email protected]>'\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down
6 changes: 2 additions & 4 deletions lms/jinja2/lms/courses/course_class_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ <h2 class="content-title mb-20">
<div class="row">
<div class="col-xs-2">{{ forms.field(form['type']) }}</div>
<div class="col-xs-3">{{ forms.field(form['venue']) }}</div>
<div class="col-xs-4">{{ forms.field(form['translation_link']) }}</div>
</div>
<div class="form-group">
<div class="form-inline">
Expand All @@ -41,10 +42,8 @@ <h2 class="content-title mb-20">
</fieldset>
{{ forms.field(form['name']) }}
{{ forms.field(form['description']) }}
{{ forms.field(form['recording_link']) }}
<fieldset>
<legend>{% trans %}Materials{% endtrans %}</legend>
{{ forms.field(form['slides']) }}
{{ forms.field(form['video_url']) }}
{{ forms.field(form['attachments']) }}
{% if form.instance -%}
{% with course_class_attachments = form.instance.courseclassattachment_set.all() -%}
Expand All @@ -57,7 +56,6 @@ <h2 class="content-title mb-20">
{%- endif %}
{%- endwith %}
{%- endif %}
{{ forms.field(form['other_materials']) }}
</fieldset>
<fieldset>
<legend>{% trans %}Visibility Settings{% endtrans %}</legend>
Expand Down
Loading

0 comments on commit f4830e8

Please sign in to comment.