From a84b7d22ba707c302f02cdc86a2709b29e054bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5?= <39742182+Dmi4er4@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:18:07 +0300 Subject: [PATCH] Yd dump cron (#919) * cron done * add yadisk lib * update cron * Halfway here * Done * update pipfile --- Pipfile | 1 + Pipfile.lock | 33 +- .../admission/locale/ru/LC_MESSAGES/django.po | 2 +- apps/api/admin.py | 11 +- apps/api/management/__init__.py | 0 apps/api/management/commands/__init__.py | 0 .../commands/dump_to_yandex_disk.py | 60 +++ .../migrations/0002_externalservicetoken.py | 29 ++ apps/api/models.py | 18 + .../htmlpages/locale/ru/LC_MESSAGES/django.po | 2 +- apps/projects/locale/ru/LC_MESSAGES/django.po | 2 +- apps/surveys/locale/ru/LC_MESSAGES/django.po | 2 +- .../locale/ru/LC_MESSAGES/django.po | 2 +- .../locale/ru/LC_MESSAGES/django.po | 2 +- .../periodic-tasks/dump_to_yandex_disk.yaml | 28 ++ locale/ru/LC_MESSAGES/django.po | 377 ++++++++++-------- 16 files changed, 391 insertions(+), 178 deletions(-) create mode 100644 apps/api/management/__init__.py create mode 100644 apps/api/management/commands/__init__.py create mode 100644 apps/api/management/commands/dump_to_yandex_disk.py create mode 100644 apps/api/migrations/0002_externalservicetoken.py create mode 100644 lk_yandexdataschool_ru/k8s/templates/periodic-tasks/dump_to_yandex_disk.yaml diff --git a/Pipfile b/Pipfile index 33f8ba2f61..2b1309912f 100644 --- a/Pipfile +++ b/Pipfile @@ -89,6 +89,7 @@ cryptography = "*" # API djangorestframework = "==3.13.1" rest-pandas = "==1.1.0" +yadisk = "==3.1.0" # Misc django-admin-env-notice = "==1.0" # https://github.com/dizballanze/django-admin-env-notice python-json-logger = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 2fecf6076c..0ff4f66426 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "74cf080ae661cc08531f644e8154207d7e2b02f116144e81c2b6ad447bdbe8f7" + "sha256": "4120f2c3d71e86cfa1fc3d571f0fa23d1d212f339fbb95e5788c8e09c47bc416" }, "pipfile-spec": 6, "requires": { @@ -77,20 +77,20 @@ }, "boto3": { "hashes": [ - "sha256:a09871805f8e462349a1c33c23eb413668df0bf68424e61d53518e1a7d883b2f", - "sha256:cc819cdbccbc2d0dc185f1dcfe74cf3809489c4cae63c2e5d6a557aa0c5ab928" + "sha256:1fa26217cd33ded82e55aed4460cd55f7223fa647916aa0d3c5d6828e6ec7135", + "sha256:a673b0b6378c9ccbf045a31a43195b175e12aa5c37fb7635fcbfc8f48fb857b3" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.77" + "version": "==1.35.79" }, "botocore": { "hashes": [ - "sha256:17b778016644e9342ca3ff2f430c1d1db0c6126e9b41a57cff52ac58e7a455e0", - "sha256:3faa27d65841499762228902d7e215fa99a4c2fdc76c9113e1c3f339bdf685b8" + "sha256:245bfdda1b1508539ddd1819c67a8a2cc81780adf0715d3de418d64c4247f346", + "sha256:e6b10bb9a357e3f5ca2e60f6dd15a85d311b9a476eb21b3c0c2a3b364a2897c8" ], "markers": "python_version >= '3.8'", - "version": "==1.35.77" + "version": "==1.35.79" }, "brotli": { "hashes": [ @@ -291,7 +291,6 @@ "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", - "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385", "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", @@ -299,7 +298,6 @@ "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", - "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba", "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", @@ -1435,12 +1433,12 @@ }, "python-json-logger": { "hashes": [ - "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c", - "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd" + "sha256:2c11056458d3f56614480b24e9cb28f7aba69cbfbebddbb77c92f0ec0d4947ab", + "sha256:d73522ddcfc6d0461394120feaddea9025dc64bf804d96357dd42fa878cc5fe8" ], "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==2.0.7" + "markers": "python_version >= '3.8'", + "version": "==3.2.0" }, "python-ldap": { "hashes": [ @@ -1959,6 +1957,15 @@ ], "version": "==1.3.0" }, + "yadisk": { + "hashes": [ + "sha256:7228fad42007e1ff8becc48ca9bfa357398f68fa50fe597672d4bf92d99e6685", + "sha256:cddc3a07f541bb005f135e8fbeff6980797494e54b6703c26830ca64d38e00e1" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.1.0" + }, "zopfli": { "hashes": [ "sha256:08ca761a8be61d3e46807adef8e8dad314ab93c824fc4d882f9e14a67d59fe57", diff --git a/apps/admission/locale/ru/LC_MESSAGES/django.po b/apps/admission/locale/ru/LC_MESSAGES/django.po index 1f7f089a43..30acd3e8d0 100644 --- a/apps/admission/locale/ru/LC_MESSAGES/django.po +++ b/apps/admission/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-30 10:17+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" "PO-Revision-Date: 2024-07-23 17:04+0000\n" "Last-Translator: Дмитрий Чернушевич \n" "Language-Team: LANGUAGE \n" diff --git a/apps/api/admin.py b/apps/api/admin.py index cc3ed57ea8..f9e4caee30 100644 --- a/apps/api/admin.py +++ b/apps/api/admin.py @@ -3,7 +3,7 @@ from api.services import TokenService -from .models import Token +from .models import ExternalServiceToken, Token @admin.register(Token) @@ -24,3 +24,12 @@ def save_model(self, request, obj, form, change): msg = _("New secret token %s has been created. Save it somewhere " "since you see it here for the last time.") messages.add_message(request, messages.WARNING, msg % secret_token) + +@admin.register(ExternalServiceToken) +class ExternalServiceTokenAdmin(admin.ModelAdmin): + list_display = ('service_tag', 'created') + fields = ('service_tag', 'access_key') + ordering = ('-created',) + + def get_readonly_fields(self, request, obj=None): + return ['service_tag'] if obj else [] diff --git a/apps/api/management/__init__.py b/apps/api/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/api/management/commands/__init__.py b/apps/api/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/api/management/commands/dump_to_yandex_disk.py b/apps/api/management/commands/dump_to_yandex_disk.py new file mode 100644 index 0000000000..ae0eed2ec4 --- /dev/null +++ b/apps/api/management/commands/dump_to_yandex_disk.py @@ -0,0 +1,60 @@ +import csv +import datetime +import io +import yadisk +from django.core.management import BaseCommand +from django.db.models import Max +from django.utils.translation import gettext_lazy as _ + +from courses.models import Assignment +from learning.models import StudentAssignment +from users.models import StudentProfile, StudentTypes +from api.models import ExternalServiceToken + +def get_max_assignment_grade(assignment: Assignment): + assert isinstance(assignment, Assignment), f"Assignment object expected, {type(assignment)} object found" + return assignment.studentassignment_set.all().aggregate(Max('score'))['score__max'] + + +class Command(BaseCommand): + help = "Dump enrollments csv and upload to yandex disk" + + def handle(self, *args, **options): + with io.StringIO() as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow([_('Student ID'), _('Curriculum year'), _('Semester'), _('Course'), _('Branch'), _('Student type'), + _('Student Group'), _('Teacher'), _('Assignment'), _('Assignment status'), _('Assignment Grade'), + _('Maximum score'), _('Assignment count'), _('Maximum student score'), _('Grade'), _('Grade re-credited')]) + + current_year = datetime.datetime.now().year + student_profiles = (StudentProfile.objects.filter(type__in=[StudentTypes.REGULAR, StudentTypes.PARTNER], + year_of_curriculum__in=[current_year - 1, current_year]) + .select_related('user') + .prefetch_related('enrollment_set__course__assignment_set__studentassignment_set')) + max_assignment_grades: dict[Assignment, int] = dict() + for student_profile in student_profiles: + user = student_profile.user + for enrollment in student_profile.enrollment_set.all(): + course = enrollment.course + assignments = course.assignment_set.all() + for assignment in assignments: + try: + student_assignment = StudentAssignment.objects.get(assignment=assignment, student=user) + if assignment not in max_assignment_grades: + max_assignment_grades[assignment] = get_max_assignment_grade(assignment) + max_assignment_grade = max_assignment_grades[assignment] + teacher = student_assignment.assignee.teacher if student_assignment.assignee is not None else "" + csv_writer.writerow([user.id, student_profile.year_of_curriculum, course.semester, course.meta_course, student_profile.branch.name, student_profile.get_type_display(), + enrollment.student_group, teacher, assignment.title, student_assignment.get_status_display(), student_assignment.score, + assignment.maximum_score, len(assignments), max_assignment_grade, enrollment.grade_honest, enrollment.is_grade_recredited]) + except StudentAssignment.DoesNotExist: + # No student assignment for assignment {assignment} in course {course} and user {user} + pass + + csv_file.seek(0) + client = yadisk.Client(token=ExternalServiceToken.objects.get(service_tag="syrop_yandex_disk").access_key) + with client: + if not client.check_token(): + raise AssertionError("Token seems to ne invalid. Is it expired?") + client.upload(io.BytesIO(csv_file.getvalue().encode()), + f"/ysda_weekly_dump/dump_{datetime.datetime.now().strftime('%d_%m_%Y')}.csv") diff --git a/apps/api/migrations/0002_externalservicetoken.py b/apps/api/migrations/0002_externalservicetoken.py new file mode 100644 index 0000000000..0a76dfa09f --- /dev/null +++ b/apps/api/migrations/0002_externalservicetoken.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.18 on 2024-12-10 13:55 + +from django.db import migrations, models +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ExternalServiceToken', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('service_tag', models.CharField(max_length=100, verbose_name='External service tag')), + ('access_key', models.CharField(db_index=True, help_text='Plain external service token', max_length=100, verbose_name='External token')), + ], + options={ + 'verbose_name': 'External token', + 'verbose_name_plural': 'External tokens', + }, + ), + ] diff --git a/apps/api/models.py b/apps/api/models.py index cbd7fe800a..27b9788e0c 100644 --- a/apps/api/models.py +++ b/apps/api/models.py @@ -34,3 +34,21 @@ class Meta: def __str__(self): return '%s : %s' % (self.access_key, self.user) + +class ExternalServiceToken(TimeStampedModel): + """ + This model stores plain external service token + """ + service_tag = models.CharField(_("External service tag"), max_length=100) + access_key = models.CharField( + verbose_name=_("External token"), + max_length=100, + db_index=True, + help_text=_("Plain external service token")) + + class Meta: + verbose_name = _("External token") + verbose_name_plural = _("External tokens") + + def __str__(self): + return f"External token for {self.service_tag}" \ No newline at end of file diff --git a/apps/htmlpages/locale/ru/LC_MESSAGES/django.po b/apps/htmlpages/locale/ru/LC_MESSAGES/django.po index 559490354d..a52b44d69a 100644 --- a/apps/htmlpages/locale/ru/LC_MESSAGES/django.po +++ b/apps/htmlpages/locale/ru/LC_MESSAGES/django.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-30 10:17+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" "PO-Revision-Date: 2015-03-18 08:34+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Russian (http://www.transifex.com/projects/p/django/language/" diff --git a/apps/projects/locale/ru/LC_MESSAGES/django.po b/apps/projects/locale/ru/LC_MESSAGES/django.po index 936c1dbd41..11f78663fa 100644 --- a/apps/projects/locale/ru/LC_MESSAGES/django.po +++ b/apps/projects/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-30 10:17+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" "PO-Revision-Date: 2022-02-21 15:24+0000\n" "Last-Translator: Сергей Жеревчук \n" "Language-Team: LANGUAGE \n" diff --git a/apps/surveys/locale/ru/LC_MESSAGES/django.po b/apps/surveys/locale/ru/LC_MESSAGES/django.po index 6fd61ea33c..b428cdc64c 100644 --- a/apps/surveys/locale/ru/LC_MESSAGES/django.po +++ b/apps/surveys/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-30 10:17+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" "PO-Revision-Date: 2019-10-31 16:30+0000\n" "Last-Translator: b' '\n" "Language-Team: LANGUAGE \n" diff --git a/compscicenter_ru/locale/ru/LC_MESSAGES/django.po b/compscicenter_ru/locale/ru/LC_MESSAGES/django.po index a17ee1879c..80a0904827 100644 --- a/compscicenter_ru/locale/ru/LC_MESSAGES/django.po +++ b/compscicenter_ru/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-30 10:17+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" "PO-Revision-Date: 2020-02-03 16:52+0000\n" "Last-Translator: b' '\n" "Language-Team: LANGUAGE \n" diff --git a/compsciclub_ru/locale/ru/LC_MESSAGES/django.po b/compsciclub_ru/locale/ru/LC_MESSAGES/django.po index d3e9144d2d..66901bf4df 100644 --- a/compsciclub_ru/locale/ru/LC_MESSAGES/django.po +++ b/compsciclub_ru/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-30 10:17+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" "PO-Revision-Date: 2020-09-09 04:43+0000\n" "Last-Translator: b' '\n" "Language-Team: LANGUAGE \n" diff --git a/lk_yandexdataschool_ru/k8s/templates/periodic-tasks/dump_to_yandex_disk.yaml b/lk_yandexdataschool_ru/k8s/templates/periodic-tasks/dump_to_yandex_disk.yaml new file mode 100644 index 0000000000..494e449bb1 --- /dev/null +++ b/lk_yandexdataschool_ru/k8s/templates/periodic-tasks/dump_to_yandex_disk.yaml @@ -0,0 +1,28 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: dump_to_yandex_disk + namespace: "{{ k8s_namespace}}" +spec: + # https://crontab.guru/#0_10_*_*_0 + schedule: "0 10 * * 0" + concurrencyPolicy: Replace + suspend: false + successfulJobsHistoryLimit: 0 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + template: + metadata: + labels: + name: dump_to_yandex_disk + spec: + containers: + - name: dump_to_yandex_disk + image: "{{ docker_registry }}/{{ backend_django_image_name }}:{{ backend_django_image_tag }}" + imagePullPolicy: IfNotPresent + command: [ "/bin/sh" ] + args: [ "-c", "python manage.py dump_to_yandex_disk" ] + env: + {% filter indent(width=16) %}{% include 'app-env.yaml' %}{% endfilter %} + restartPolicy: Never diff --git a/locale/ru/LC_MESSAGES/django.po b/locale/ru/LC_MESSAGES/django.po index 40e472331a..b5785c0532 100644 --- a/locale/ru/LC_MESSAGES/django.po +++ b/locale/ru/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-01 13:42+0000\n" -"PO-Revision-Date: 2024-12-01 13:42+0000\n" +"POT-Creation-Date: 2024-12-12 14:45+0000\n" +"PO-Revision-Date: 2024-12-12 14:46+0000\n" "Last-Translator: Дмитрий Чернушевич \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -51,6 +51,121 @@ msgstr "Пользователь деактивирован или удалён. msgid "Token is invalid or expired" msgstr "" +#: apps/api/management/commands/dump_to_yandex_disk.py:25 +#| msgid "Student" +msgid "Student ID" +msgstr "ID Студента" + +#: apps/api/management/commands/dump_to_yandex_disk.py:25 +#: lms/jinja2/lms/user_profile/_tab_student_profiles.html:136 +msgid "Curriculum year" +msgstr "Год программы обучения" + +#: apps/api/management/commands/dump_to_yandex_disk.py:25 +#: apps/courses/models.py:115 apps/courses/models.py:319 +#: apps/learning/gallery/models.py:83 apps/learning/models.py:285 +#: apps/learning/models.py:557 apps/users/models.py:1137 +#: lms/jinja2/lms/courses/meta_detail.html:26 +#: lms/jinja2/lms/courses/teacher_detail.html:32 +#: lms/jinja2/lms/study/course_list.html:140 +#: lms/jinja2/lms/teaching/course_list.html:11 +msgid "Semester" +msgstr "Семестр" + +#: apps/api/management/commands/dump_to_yandex_disk.py:25 +#: apps/courses/models.py:230 apps/courses/models.py:278 +#: apps/courses/models.py:715 apps/courses/models.py:843 +#: apps/courses/models.py:926 apps/learning/admin.py:29 +#: apps/learning/admin.py:273 apps/learning/study/forms.py:77 +#: lms/jinja2/lms/courses/teacher_detail.html:31 +#: lms/jinja2/lms/learning/timetable.html:34 +#: lms/jinja2/lms/study/assignment_list.html:26 +#: lms/jinja2/lms/study/assignment_list.html:88 +#: lms/jinja2/lms/teaching/timetable.html:36 +msgid "Course" +msgstr "Курс" + +#: apps/api/management/commands/dump_to_yandex_disk.py:25 +#: apps/core/models.py:291 apps/courses/models.py:61 +#: apps/courses/models.py:255 apps/courses/models.py:711 +#: apps/learning/admin.py:83 apps/learning/gradebook/views.py:237 +#: apps/learning/models.py:78 apps/learning/models.py:1092 +#: apps/library/models.py:66 apps/staff/filters.py:130 +#: apps/staff/filters.py:205 apps/staff/views.py:917 apps/staff/views.py:981 +#: apps/study_programs/models.py:65 apps/users/models.py:176 +#: apps/users/models.py:358 apps/users/models.py:815 +#: lms/jinja2/lms/courses/meta_detail.html:27 +#: lms/jinja2/lms/staff/academic_discipline_log.html:19 +#: lms/jinja2/lms/staff/status_log.html:19 +#: lms/jinja2/lms/user_profile/_tab_student_profiles.html:44 +msgid "Branch" +msgstr "Отделение" + +#: apps/api/management/commands/dump_to_yandex_disk.py:25 +#, fuzzy +#| msgid "Student" +msgid "Student type" +msgstr "Студент" + +#: apps/api/management/commands/dump_to_yandex_disk.py:26 +#: apps/learning/models.py:92 apps/learning/models.py:166 +#: apps/learning/models.py:389 +msgid "Student Group" +msgstr "Студенческая группа" + +#: apps/api/management/commands/dump_to_yandex_disk.py:26 +#: apps/courses/forms.py:506 apps/learning/roles.py:156 +#: apps/users/constants.py:37 +msgid "Teacher" +msgstr "Преподаватель" + +#: apps/api/management/commands/dump_to_yandex_disk.py:26 +#: apps/courses/models.py:1268 apps/courses/models.py:1375 +#: apps/grading/admin.py:25 apps/learning/admin.py:292 +#: apps/learning/models.py:175 apps/learning/models.py:216 +#: apps/learning/models.py:241 +#: lms/jinja2/lms/user_profile/_tab_assignments.html:11 +msgid "Assignment" +msgstr "Задание" + +#: apps/api/management/commands/dump_to_yandex_disk.py:26 +#| msgid "AssignmentStatus|New" +msgid "Assignment status" +msgstr "Статус задания" + +#: apps/api/management/commands/dump_to_yandex_disk.py:26 +#| msgid "Assignment created" +msgid "Assignment Grade" +msgstr "Оценка по заданию" + +#: apps/api/management/commands/dump_to_yandex_disk.py:27 +#: apps/courses/forms.py:315 apps/courses/models.py:1226 +msgid "Maximum score" +msgstr "Максимальный балл" + +#: apps/api/management/commands/dump_to_yandex_disk.py:27 +#| msgid "Assignment-comment" +msgid "Assignment count" +msgstr "Количество заданий" + +#: apps/api/management/commands/dump_to_yandex_disk.py:27 +#| msgid "Maximum score" +msgid "Maximum student score" +msgstr "Максимальный полученный балл" + +#: apps/api/management/commands/dump_to_yandex_disk.py:27 +#: apps/learning/models.py:628 lms/jinja2/lms/study/course_list.html:139 +#: lms/jinja2/lms/study/student_assignment_detail.html:122 +#: lms/jinja2/lms/teaching/student_assignment_detail.html:181 +#: lms/jinja2/lms/user_profile/_tab_assignments.html:12 +msgid "Grade" +msgstr "Оценка" + +#: apps/api/management/commands/dump_to_yandex_disk.py:27 +#: apps/learning/models.py:372 +msgid "Grade re-credited" +msgstr "Оценка перезачтена" + #: apps/api/models.py:17 msgid "Access Key" msgstr "Ключ доступа" @@ -79,6 +194,24 @@ msgstr "Токен" msgid "Tokens" msgstr "Токены" +#: apps/api/models.py:42 +msgid "External service tag" +msgstr "Тег внешнего сервиса" + +#: apps/api/models.py:44 apps/api/models.py:50 +msgid "External token" +msgstr "Внешний токен" + +#: apps/api/models.py:47 +#, fuzzy +#| msgid "External services" +msgid "Plain external service token" +msgstr "Внешние сервисы" + +#: apps/api/models.py:51 +msgid "External tokens" +msgstr "Внешние токены" + #: apps/api/serializers.py:35 msgid "No active account found" msgstr "" @@ -260,7 +393,7 @@ msgstr "Сайт" #: apps/core/models.py:279 apps/core/models.py:334 apps/courses/forms.py:172 #: apps/courses/models.py:70 apps/courses/models.py:339 -#: apps/courses/models.py:955 apps/learning/models.py:1102 +#: apps/courses/models.py:943 apps/learning/models.py:1102 #: apps/templates/learning/event_detail.html:29 #: lms/jinja2/lms/courses/course_class_detail.html:55 msgid "Description" @@ -270,21 +403,6 @@ msgstr "Описание" msgid "Branch|Description" msgstr "Общее описание, правила обучения" -#: apps/core/models.py:291 apps/courses/models.py:61 -#: apps/courses/models.py:255 apps/courses/models.py:723 -#: apps/learning/admin.py:83 apps/learning/gradebook/views.py:237 -#: apps/learning/models.py:78 apps/learning/models.py:1092 -#: apps/library/models.py:66 apps/staff/filters.py:130 -#: apps/staff/filters.py:205 apps/staff/views.py:917 apps/staff/views.py:981 -#: apps/study_programs/models.py:65 apps/users/models.py:176 -#: apps/users/models.py:358 apps/users/models.py:815 -#: lms/jinja2/lms/courses/meta_detail.html:27 -#: lms/jinja2/lms/staff/academic_discipline_log.html:19 -#: lms/jinja2/lms/staff/status_log.html:19 -#: lms/jinja2/lms/user_profile/_tab_student_profiles.html:44 -msgid "Branch" -msgstr "Отделение" - #: apps/core/models.py:292 apps/courses/models.py:264 #: apps/learning/admin.py:371 apps/learning/models.py:561 msgid "Branches" @@ -310,8 +428,8 @@ msgstr "Как добраться" msgid "Flags" msgstr "Флаги" -#: apps/core/models.py:343 apps/courses/models.py:1003 -#: apps/courses/models.py:1168 +#: apps/core/models.py:343 apps/courses/models.py:991 +#: apps/courses/models.py:1156 msgid "Class" msgstr "Занятие" @@ -413,7 +531,7 @@ msgstr "" "Пожалуйста, перепроверьте список выбранных преподавателей. Кто-то из них не " "имеет отношения к выбранному прочтению курса." -#: apps/courses/admin.py:265 apps/courses/models.py:1221 +#: apps/courses/admin.py:265 apps/courses/models.py:1209 msgid "Assignment|deadline" msgstr "Срок сдачи" @@ -605,14 +723,14 @@ msgstr "Текст" msgid "Venue" msgstr "Место проведения" -#: apps/courses/forms.py:165 apps/courses/models.py:945 +#: apps/courses/forms.py:165 apps/courses/models.py:933 #: apps/notifications/models.py:92 apps/staff/filters.py:135 #: apps/staff/filters.py:210 apps/staff/views.py:917 apps/staff/views.py:981 #: apps/users/models.py:795 msgid "Type" msgstr "Тип" -#: apps/courses/forms.py:168 apps/courses/models.py:953 +#: apps/courses/forms.py:168 apps/courses/models.py:941 #: apps/learning/models.py:1100 msgid "CourseClass|Name" msgstr "Название" @@ -633,7 +751,7 @@ msgstr "Материалы (презентации, инструкции, пам msgid "You can select multiple files" msgstr "Вы можете выбрать несколько файлов" -#: apps/courses/forms.py:182 apps/courses/models.py:948 +#: apps/courses/forms.py:182 apps/courses/models.py:936 #: apps/learning/models.py:1105 msgid "Date" msgstr "Дата" @@ -642,7 +760,7 @@ msgstr "Дата" msgid "Format: dd.mm.yyyy" msgstr "Формат: дд.мм.гггг" -#: apps/courses/forms.py:187 apps/courses/models.py:949 +#: apps/courses/forms.py:187 apps/courses/models.py:937 #: apps/learning/models.py:1106 msgid "Starts at" msgstr "Начало" @@ -651,13 +769,13 @@ msgstr "Начало" msgid "Format: hh:mm" msgstr "Формат: чч:мм" -#: apps/courses/forms.py:191 apps/courses/models.py:950 +#: apps/courses/forms.py:191 apps/courses/models.py:938 #: apps/learning/models.py:1107 msgid "Ends at" msgstr "Конец" #: apps/courses/forms.py:195 apps/courses/forms.py:304 -#: apps/courses/models.py:951 apps/courses/models.py:1222 +#: apps/courses/models.py:939 apps/courses/models.py:1210 #: apps/users/models.py:362 lms/jinja2/lms/user_profile/_tab_account.html:133 msgid "Time Zone" msgstr "Часовой пояс" @@ -711,17 +829,13 @@ msgstr "Срок сдачи" msgid "Attached files" msgstr "Приложенные файлы" -#: apps/courses/forms.py:312 apps/courses/models.py:1234 +#: apps/courses/forms.py:312 apps/courses/models.py:1222 #: lms/jinja2/lms/study/student_assignment_detail.html:147 #: lms/jinja2/lms/teaching/student_assignment_detail.html:240 msgid "Passing score" msgstr "Проходной балл" -#: apps/courses/forms.py:315 apps/courses/models.py:1238 -msgid "Maximum score" -msgstr "Максимальный балл" - -#: apps/courses/forms.py:318 apps/courses/models.py:1242 +#: apps/courses/forms.py:318 apps/courses/models.py:1230 #: lms/jinja2/lms/study/student_assignment_detail.html:150 #: lms/jinja2/lms/teaching/assignment_detail.html:45 #: lms/jinja2/lms/teaching/student_assignment_detail.html:245 @@ -734,11 +848,11 @@ msgid "" "the gradebook." msgstr "Вклад задания в сумму баллов по курсу." -#: apps/courses/forms.py:325 apps/courses/models.py:1247 +#: apps/courses/forms.py:325 apps/courses/models.py:1235 msgid "Time to Completion" msgstr "Время для завершения" -#: apps/courses/forms.py:327 apps/courses/models.py:1249 +#: apps/courses/forms.py:327 apps/courses/models.py:1237 msgid "Estimated amount of time required for the task to be completed" msgstr "" "Сколько, по вашему мнению, студенту потребуется времени, чтобы выполнить " @@ -757,7 +871,7 @@ msgid "Restrict assignment to selected groups. Available to all by default." msgstr "" "Задание будет доступно только выбранным группам. По умолчанию доступно всем." -#: apps/courses/forms.py:337 apps/courses/models.py:1269 +#: apps/courses/forms.py:337 apps/courses/models.py:1257 #: apps/grading/models.py:32 apps/grading/models.py:52 msgid "Checking System" msgstr "Проверяющая система" @@ -782,11 +896,6 @@ msgstr "Режим назначения" msgid "URL is specified but checking system is empty" msgstr "Для URL не указана проверяющая система" -#: apps/courses/forms.py:506 apps/learning/roles.py:156 -#: apps/users/constants.py:37 -msgid "Teacher" -msgstr "Преподаватель" - #: apps/courses/forms.py:551 apps/learning/models.py:226 #: apps/users/models.py:1134 lms/jinja2/lms/courses/course_detail.html:114 #: lms/jinja2/lms/courses/course_detail.html:293 @@ -833,16 +942,6 @@ msgstr "Порядковый номер семестра (служебное п msgid "System field. Used for sort order and filter." msgstr "Системное поле. Используется для сортировки и фильтрации." -#: apps/courses/models.py:115 apps/courses/models.py:319 -#: apps/learning/gallery/models.py:83 apps/learning/models.py:285 -#: apps/learning/models.py:557 apps/users/models.py:1137 -#: lms/jinja2/lms/courses/meta_detail.html:26 -#: lms/jinja2/lms/courses/teacher_detail.html:32 -#: lms/jinja2/lms/study/course_list.html:140 -#: lms/jinja2/lms/teaching/course_list.html:11 -msgid "Semester" -msgstr "Семестр" - #: apps/courses/models.py:116 msgid "Semesters" msgstr "Семестры" @@ -867,18 +966,6 @@ msgstr "Краткое описание" msgid "MetaCourse|cover" msgstr "Обложка" -#: apps/courses/models.py:230 apps/courses/models.py:278 -#: apps/courses/models.py:727 apps/courses/models.py:855 -#: apps/courses/models.py:938 apps/learning/admin.py:29 -#: apps/learning/admin.py:273 apps/learning/study/forms.py:77 -#: lms/jinja2/lms/courses/teacher_detail.html:31 -#: lms/jinja2/lms/learning/timetable.html:34 -#: lms/jinja2/lms/study/assignment_list.html:26 -#: lms/jinja2/lms/study/assignment_list.html:88 -#: lms/jinja2/lms/teaching/timetable.html:36 -msgid "Course" -msgstr "Курс" - #: apps/courses/models.py:252 msgid "System" msgstr "Системный" @@ -1048,11 +1135,11 @@ msgstr "Публиковать в разделе «Видео»" msgid "Do we see this course in certificates and diplomas?" msgstr "Отображается ли курс в справках и дипломах" -#: apps/courses/models.py:376 apps/courses/models.py:730 +#: apps/courses/models.py:376 apps/courses/models.py:718 msgid "Main Branch" msgstr "Главное отделение" -#: apps/courses/models.py:381 apps/courses/models.py:735 +#: apps/courses/models.py:381 apps/courses/models.py:723 msgid "Course Branches" msgstr "Отделения курса" @@ -1077,7 +1164,7 @@ msgstr "Язык" msgid "The language in which lectures are given." msgstr "Укажите язык прочтения лекций." -#: apps/courses/models.py:399 apps/courses/models.py:983 +#: apps/courses/models.py:399 apps/courses/models.py:971 msgid "Materials Visibility" msgstr "Видимость материалов" @@ -1085,7 +1172,7 @@ msgstr "Видимость материалов" msgid "Default visibility for class materials." msgstr "Видимость материалов занятий по умолчанию" -#: apps/courses/models.py:405 apps/courses/models.py:971 +#: apps/courses/models.py:405 apps/courses/models.py:959 msgid "Translation link" msgstr "Ссылка на трансляцию" @@ -1097,7 +1184,7 @@ msgstr "Публичные слайды" msgid "Public Attachments" msgstr "Публичные приложения" -#: apps/courses/models.py:437 apps/courses/models.py:1219 +#: apps/courses/models.py:437 apps/courses/models.py:1207 #: apps/learning/gallery/models.py:49 apps/learning/models.py:68 #: apps/learning/models.py:359 apps/learning/models.py:487 #: lms/jinja2/lms/user_profile/_tab_assignments.html:10 @@ -1108,165 +1195,157 @@ msgstr "Прочтение курса" msgid "Course offerings" msgstr "Прочтения курсов" -#: apps/courses/models.py:522 apps/learning/models.py:327 -msgid "Start of the enrollment period should be inside term boundaries" -msgstr "Начало периода записи должно быть в пределах семестра" - -#: apps/courses/models.py:527 -msgid "End of the enrollment period should be inside term boundaries" -msgstr "Конец периода записи должен быть в пределах семестра" - -#: apps/courses/models.py:531 apps/learning/models.py:336 +#: apps/courses/models.py:519 apps/learning/models.py:336 msgid "Deadline should be later than the start of the enrollment period" msgstr "" "Последний день записи на курсы должен быть не раньше первого дня записи на " "курсы" -#: apps/courses/models.py:535 +#: apps/courses/models.py:523 msgid "You can not set listeners capacity with REGULAR enrollment type" msgstr "Вы не можете выставить максимальное кол-во слушателей с типом \"Сдавать\"" -#: apps/courses/models.py:538 +#: apps/courses/models.py:526 msgid "You can not set learners capacity with LECTIONS_ONLY enrollment type" msgstr "" "Вы не можете выставить максимальное кол-во сдающих с типом \"Только " "слушать\"" -#: apps/courses/models.py:734 +#: apps/courses/models.py:722 msgid "Course Branch" msgstr "Отделение курса" -#: apps/courses/models.py:767 apps/notifications/models.py:194 +#: apps/courses/models.py:755 apps/notifications/models.py:194 msgid "Notifications" msgstr "Уведомления" -#: apps/courses/models.py:773 +#: apps/courses/models.py:761 msgid "Course Teacher" msgstr "Преподаватель курса" -#: apps/courses/models.py:774 +#: apps/courses/models.py:762 msgid "Course Teachers" msgstr "Преподаватели курса" -#: apps/courses/models.py:831 apps/courses/models.py:860 +#: apps/courses/models.py:819 apps/courses/models.py:848 #: apps/learning/models.py:466 apps/learning/models.py:887 #: apps/users/models.py:1028 msgid "Author" msgstr "Автор" -#: apps/courses/models.py:835 +#: apps/courses/models.py:823 msgid "CourseReview|text" msgstr "Текст" -#: apps/courses/models.py:843 +#: apps/courses/models.py:831 msgid "Course Review" msgstr "Отзыв о курсе" -#: apps/courses/models.py:844 +#: apps/courses/models.py:832 msgid "Course Reviews" msgstr "Отзывы о курсе" -#: apps/courses/models.py:857 +#: apps/courses/models.py:845 msgid "CourseNews|title" msgstr "Заголовок" -#: apps/courses/models.py:863 +#: apps/courses/models.py:851 msgid "CourseNews|text" msgstr "Текст" -#: apps/courses/models.py:868 +#: apps/courses/models.py:856 msgid "Course news-singular" msgstr "Новость" -#: apps/courses/models.py:869 +#: apps/courses/models.py:857 msgid "Course news-plural" msgstr "Новости" -#: apps/courses/models.py:942 apps/learning/models.py:1097 +#: apps/courses/models.py:930 apps/learning/models.py:1097 msgid "CourseClass|Venue" msgstr "Место проведения" -#: apps/courses/models.py:959 +#: apps/courses/models.py:947 #: lms/jinja2/lms/courses/course_class_detail.html:48 msgid "Slides" msgstr "Слайды" -#: apps/courses/models.py:964 +#: apps/courses/models.py:952 msgid "SlideShare URL" msgstr "SlideShare URL" -#: apps/courses/models.py:966 +#: apps/courses/models.py:954 msgid "Video Recording" msgstr "Видео" -#: apps/courses/models.py:968 +#: apps/courses/models.py:956 msgid "Both YouTube and Yandex Video are supported" msgstr "Поддерживаются ссылки на YouTube и Яндекс.Видео" -#: apps/courses/models.py:975 +#: apps/courses/models.py:963 #: lms/jinja2/lms/courses/course_class_detail.html:69 msgid "Recording link" msgstr "Ссылка на запись" -#: apps/courses/models.py:979 +#: apps/courses/models.py:967 msgid "CourseClass|Other materials" msgstr "Другие материалы" -#: apps/courses/models.py:985 +#: apps/courses/models.py:973 msgid "Slides, attachments and other materials" msgstr "Слайды, приложенные файлы и др." -#: apps/courses/models.py:989 apps/courses/models.py:1263 +#: apps/courses/models.py:977 apps/courses/models.py:1251 #: apps/learning/gradebook/forms.py:120 apps/learning/models.py:222 #: lms/jinja2/lms/staff/student_search.html:59 msgid "Groups" msgstr "Группы" -#: apps/courses/models.py:994 +#: apps/courses/models.py:982 msgid "Course class teachers" msgstr "Преподаватели занятия" -#: apps/courses/models.py:998 lms/jinja2/lms/courses/course_detail.html:151 +#: apps/courses/models.py:986 lms/jinja2/lms/courses/course_detail.html:151 msgid "Is conducted by invited" msgstr "Приглашенный спикер" -#: apps/courses/models.py:1004 apps/learning/icalendar.py:256 +#: apps/courses/models.py:992 apps/learning/icalendar.py:256 msgid "Classes" msgstr "Занятия" -#: apps/courses/models.py:1019 +#: apps/courses/models.py:1007 msgid "Class should end after it started" msgstr "Конец занятия должен быть позже начала" -#: apps/courses/models.py:1120 +#: apps/courses/models.py:1108 msgid "slides" msgstr "слайды" -#: apps/courses/models.py:1124 +#: apps/courses/models.py:1112 msgid "video" msgstr "видео" -#: apps/courses/models.py:1132 +#: apps/courses/models.py:1120 msgid "files" msgstr "файлы" -#: apps/courses/models.py:1136 +#: apps/courses/models.py:1124 msgid "other" msgstr "другое" -#: apps/courses/models.py:1139 +#: apps/courses/models.py:1127 msgid "recording" msgstr "запись" -#: apps/courses/models.py:1177 +#: apps/courses/models.py:1165 msgid "Class attachment" msgstr "Приложение к занятию" -#: apps/courses/models.py:1178 +#: apps/courses/models.py:1166 msgid "Class attachments" msgstr "Приложения к занятию" -#: apps/courses/models.py:1225 apps/learning/study/forms.py:62 +#: apps/courses/models.py:1213 apps/learning/study/forms.py:62 #: lms/jinja2/lms/courses/course_detail.html:177 #: lms/jinja2/lms/study/assignment_list.html:28 #: lms/jinja2/lms/study/assignment_list.html:90 @@ -1276,56 +1355,48 @@ msgstr "Приложения к занятию" msgid "Assignment Format" msgstr "Формат задания" -#: apps/courses/models.py:1229 +#: apps/courses/models.py:1217 msgid "Assignment|name" msgstr "Название" -#: apps/courses/models.py:1231 +#: apps/courses/models.py:1219 msgid "Assignment|text" msgstr "Текст" -#: apps/courses/models.py:1251 +#: apps/courses/models.py:1239 msgid "Assignee mode" msgstr "Режим назначения" -#: apps/courses/models.py:1252 +#: apps/courses/models.py:1240 msgid "Automatic assignment mode of a responsible teacher" msgstr "Режим автоматического назначения ответственного преподавателя" -#: apps/courses/models.py:1257 +#: apps/courses/models.py:1245 msgid "Assignment Assignees" msgstr "Ответственные за задание" -#: apps/courses/models.py:1258 +#: apps/courses/models.py:1246 msgid "Has lower priority than student group assignees" msgstr "Приоритет ниже, чем у ответственных за студенческие группы" -#: apps/courses/models.py:1280 apps/courses/models.py:1387 -#: apps/grading/admin.py:25 apps/learning/admin.py:292 -#: apps/learning/models.py:175 apps/learning/models.py:216 -#: apps/learning/models.py:241 -#: lms/jinja2/lms/user_profile/_tab_assignments.html:11 -msgid "Assignment" -msgstr "Задание" - -#: apps/courses/models.py:1281 apps/learning/icalendar.py:257 +#: apps/courses/models.py:1269 apps/learning/icalendar.py:257 #: lms/jinja2/lms/user_profile/_tab_assignments.html:3 msgid "Assignments" msgstr "Задания" -#: apps/courses/models.py:1290 +#: apps/courses/models.py:1278 msgid "Course modification is not allowed" msgstr "Изменение курса запрещено" -#: apps/courses/models.py:1292 +#: apps/courses/models.py:1280 msgid "Passing score should be less than (or equal to) maximum one" msgstr " Проходной балл должен быть меньше или равен максимальному баллу" -#: apps/courses/models.py:1395 +#: apps/courses/models.py:1383 msgid "Assignment attachment" msgstr "Приложение к заданию" -#: apps/courses/models.py:1396 +#: apps/courses/models.py:1384 msgid "Assignment attachments" msgstr "Приложения к заданиям" @@ -1863,11 +1934,6 @@ msgstr "Тип группы" msgid "Enrollment key" msgstr "Код записи на курс" -#: apps/learning/models.py:92 apps/learning/models.py:166 -#: apps/learning/models.py:389 -msgid "Student Group" -msgstr "Студенческая группа" - #: apps/learning/models.py:107 msgid "Branch is not specified for the `branch` group type" msgstr "" @@ -1947,6 +2013,10 @@ msgstr "Период записи на курсы" msgid "Enrollment Periods" msgstr "Периоды записи на курсы" +#: apps/learning/models.py:327 +msgid "Start of the enrollment period should be inside term boundaries" +msgstr "Начало периода записи должно быть в пределах семестра" + #: apps/learning/models.py:333 msgid "Deadline should be later than the expected term start ({})" msgstr "" @@ -1965,10 +2035,6 @@ msgstr "Профиль студента" msgid "Enrollment|grade" msgstr "Оценка" -#: apps/learning/models.py:372 -msgid "Grade re-credited" -msgstr "Оценка перезачтена" - #: apps/learning/models.py:373 msgid "" "This flag is used to represent that set grade is recredited. Use it with " @@ -2080,13 +2146,6 @@ msgstr "Студент" msgid "StudentAssignment|Status" msgstr "Статус" -#: apps/learning/models.py:628 lms/jinja2/lms/study/course_list.html:139 -#: lms/jinja2/lms/study/student_assignment_detail.html:122 -#: lms/jinja2/lms/teaching/student_assignment_detail.html:181 -#: lms/jinja2/lms/user_profile/_tab_assignments.html:12 -msgid "Grade" -msgstr "Оценка" - #: apps/learning/models.py:640 msgid "Assignment|grade changed" msgstr "Оценка изменена" @@ -2824,7 +2883,7 @@ msgstr "Залить номера пропусков" msgid "File read error: {str(e)}" msgstr "" -#: apps/staff/forms.py:95 +#: apps/staff/forms.py:95 apps/users/services.py:626 msgid "CSV file must contain \"Email\" and \"Badge number\" columns" msgstr "" "Файл CSV должен содержать колонки с заголовками «Почта» и «Номер пропуска»" @@ -3670,11 +3729,11 @@ msgstr "Справка студента" msgid "Student References" msgstr "Справки студента" -#: apps/users/services.py:530 +#: apps/users/services.py:529 msgid "Major and minor must be objects of the same types" msgstr "Основной и дублирующий объекты должны быть объектами одного типа" -#: apps/users/services.py:532 +#: apps/users/services.py:531 msgid "" "Major and minor objects must not be the same: {major} with type " "{type(major)}" @@ -3682,18 +3741,17 @@ msgstr "" "Основной и дублирующий объекты не должны быть одним объектом: {major} типа " "{type(major)}" -#: apps/users/services.py:590 +#: apps/users/services.py:589 msgid "Use merge_objects for non User instances" msgstr "Для объектов не-Пользователей используйте merge_objects" -#: apps/users/services.py:592 +#: apps/users/services.py:591 #, python-brace-format msgid "Major and minor Users must not be the same object: {major}" msgstr "" "Основной и дублирующий пользователи не должны быть одним объектом: {major}" -#: apps/users/services.py:630 -#| msgid "User with email «{email}» does not exists" +#: apps/users/services.py:633 msgid "User with email \"{}\" does not exists" msgstr "Пользователь с почтой «{}» не существует" @@ -4216,10 +4274,6 @@ msgstr "История статусов" msgid "Areas of study history" msgstr "История направлений" -#: lms/jinja2/lms/user_profile/_tab_student_profiles.html:136 -msgid "Curriculum year" -msgstr "Год программы обучения" - #: lms/jinja2/lms/user_profile/user_detail.html:6 msgid "on site" msgstr "на сайте" @@ -4251,6 +4305,13 @@ msgctxt "adjective" msgid "spring" msgstr "весенний" +#, fuzzy +#~ msgid "External Token" +#~ msgstr "Внешний ID" + +#~ msgid "End of the enrollment period should be inside term boundaries" +#~ msgstr "Конец периода записи должен быть в пределах семестра" + #~ msgid "" #~ "If the course was recredited without grade use re-credit grade only. The " #~ "flag is used to represent that set grade is recredited."