From ec0cfabb01c587e6242a1b28c735a4b87cdd6694 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: Sun, 8 Sep 2024 09:41:21 +0300 Subject: [PATCH] Add create_partner_profile script, remove former scripts; Disable creating student profile with status or academic discipline (#873) * Preparation * Complete * Delete former scipts * Minor issue * Minor issue --- .../management/commands/create_magistr_fkn.py | 86 -------- .../commands/create_magistr_mipt.py | 86 -------- .../commands/create_partner_profiles.py | 202 ++++++++++++++++++ apps/users/admin.py | 21 +- .../user_profile/_tab_student_profiles.html | 4 + 5 files changed, 220 insertions(+), 179 deletions(-) delete mode 100644 apps/admission/management/commands/create_magistr_fkn.py delete mode 100644 apps/admission/management/commands/create_magistr_mipt.py create mode 100644 apps/admission/management/commands/create_partner_profiles.py diff --git a/apps/admission/management/commands/create_magistr_fkn.py b/apps/admission/management/commands/create_magistr_fkn.py deleted file mode 100644 index 3297811bb..000000000 --- a/apps/admission/management/commands/create_magistr_fkn.py +++ /dev/null @@ -1,86 +0,0 @@ -import csv - -from django.db import transaction -from django.db.models import Q - -from core.models import Branch, Site - -from users.constants import GenderTypes -from users.models import PartnerTag, User, StudentTypes -from users.services import create_account, generate_username_from_email, get_student_profile, create_student_profile - -msk_branch = Branch.objects.get(pk=7) -msk_site = Site.objects.get(pk=3) - -def extract_credentials(credentials: str): - data = credentials.split(" ") - if len(data) == 2: - data.append("") - return data[0], data[1], data[2] - - -def main(): - print(f"Branch: {msk_branch}") - print(f"Site: {msk_site}") - gender = GenderTypes.OTHER - partner = PartnerTag.objects.get(pk=2) # МФТИ=1, ФКН=2 - with open("students_fkn.csv", "r") as csvfile: - reader = csv.reader(csvfile, delimiter=',') - next(reader, None) # skip the headers - with transaction.atomic(): - for row in reader: - last_name, first_name, patronymic = extract_credentials(row[0]) - email, track = row[1], row[2] - try: - user = User.objects.get(email__iexact=email) - print(f"Found user {user} {track}") - except User.DoesNotExist as e: - user = create_account(username=generate_username_from_email(email), - password=User.objects.make_random_password(), - email=email, - gender=gender, - time_zone=msk_branch.time_zone, - is_active=True) - user.first_name = first_name - user.last_name = last_name - user.patronymic = patronymic - user.save() - print(f"CREATED USER {user} ({track})") - - if track == "базовый": - profile = get_student_profile(user=user, - site=msk_site, - profile_type=StudentTypes.REGULAR) - if profile is not None: - print(f"Found REGULAR profile {profile}: {profile.status}") - else: - profile_fields = { - "profile_type": StudentTypes.REGULAR, - "year_of_curriculum": 2022, - "user": user, - "branch": msk_branch, - "year_of_admission": 2022, - "partner": partner - } - profile = create_student_profile(**profile_fields) - print(f"CREATED REGULAR PROFILE: {profile}") - - profile = get_student_profile(user=user, - site=msk_site, - profile_type=StudentTypes.PARTNER) - if profile is not None: - print(f"ATTENTION! Found PARTNER profile: {profile}: {profile.status}") - else: - profile_fields = { - "profile_type": StudentTypes.PARTNER, - "year_of_curriculum": 2022, - "user": user, - "branch": msk_branch, - "year_of_admission": 2022, - "partner": partner - } - profile = create_student_profile(**profile_fields) - print(f"CREATED PARTNER PROFILE: {profile}") - - -main() diff --git a/apps/admission/management/commands/create_magistr_mipt.py b/apps/admission/management/commands/create_magistr_mipt.py deleted file mode 100644 index 22963137c..000000000 --- a/apps/admission/management/commands/create_magistr_mipt.py +++ /dev/null @@ -1,86 +0,0 @@ -import csv - -from django.db import transaction -from django.db.models import Q - -from core.models import Branch, Site - -from users.constants import GenderTypes -from users.models import PartnerTag, User, StudentTypes -from users.services import create_account, generate_username_from_email, get_student_profile, create_student_profile - -msk_branch = Branch.objects.get(pk=7) -msk_site = Site.objects.get(pk=3) - -def extract_credentials(credentials: str): - data = credentials.split(" ") - if len(data) == 2: - data.append("") - return data[0], data[1], data[2] - - -def main(): - print(f"Branch: {msk_branch}") - print(f"Site: {msk_site}") - gender = GenderTypes.OTHER - partner = PartnerTag.objects.get(pk=1) # МФТИ=1, ФКН=2 - with open("students_mipt.csv", "r") as csvfile: - reader = csv.reader(csvfile, delimiter=',') - next(reader, None) # skip the headers - with transaction.atomic(): - for row in reader: - last_name, first_name, patronymic = extract_credentials(row[0]) - email, track, phone = row[1], row[2], row[3] - try: - user = User.objects.get(email__iexact=email) - print(f"Found user {user} {track}") - except User.DoesNotExist as e: - user = create_account(username=generate_username_from_email(email), - password=User.objects.make_random_password(), - email=email, - gender=gender, - time_zone=msk_branch.time_zone, - is_active=True) - user.first_name = first_name - user.last_name = last_name - user.patronymic = patronymic - print(f"CREATED USER {user} ({track})") - user.phone = phone - user.save() - - if track == "базовый": - profile = get_student_profile(user=user, - site=msk_site, - profile_type=StudentTypes.REGULAR) - if profile is not None: - print(f"Found REGULAR profile {profile}: {profile.status}") - else: - profile_fields = { - "profile_type": StudentTypes.REGULAR, - "year_of_curriculum": 2022, - "user": user, - "branch": msk_branch, - "year_of_admission": 2022, - "partner": partner - } - profile = create_student_profile(**profile_fields) - print(f"CREATED REGULAR PROFILE: {profile}") - - profile = get_student_profile(user=user, - site=msk_site, - profile_type=StudentTypes.PARTNER) - if profile is not None: - print(f"ATTENTION! Found PARTNER profile: {profile}: {profile.status}") - else: - profile_fields = { - "profile_type": StudentTypes.PARTNER, - "year_of_curriculum": 2022, - "user": user, - "branch": msk_branch, - "year_of_admission": 2022, - "partner": partner - } - profile = create_student_profile(**profile_fields) - print(f"CREATED PARTNER PROFILE: {profile}") - -main() diff --git a/apps/admission/management/commands/create_partner_profiles.py b/apps/admission/management/commands/create_partner_profiles.py new file mode 100644 index 000000000..f94b05cce --- /dev/null +++ b/apps/admission/management/commands/create_partner_profiles.py @@ -0,0 +1,202 @@ +import csv +from datetime import datetime + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction +from django.utils import timezone + +from core.models import Branch +from study_programs.models import AcademicDiscipline +from users.constants import GenderTypes +from users.models import PartnerTag, User, StudentTypes, StudentProfile, StudentAcademicDisciplineLog +from users.services import create_account, generate_username_from_email, create_student_profile + + +class Command(BaseCommand): + """ + Make partner student profile, account (if needed) and StudentAcademicDisciplineLog from csv + + Example: + ./manage.py create_partner_profiles --branch=msk --partner=mfti --filename=mfti_students.csv --entry_author_id=16376 --changed_at=01.09.2024 --trust_csv + """ + + help = """Make partner student profiles from csv""" + + def add_arguments(self, parser): + super().add_arguments(parser) + parser.add_argument( + "--branch", + type=str, + help="Branch code to fill it in profile" + ) + parser.add_argument( + "--partner", + type=str, + help="Partner slug to fill it in profile" + ) + parser.add_argument( + "--filename", + type=str, + help="Csv file name", + ) + parser.add_argument( + "--delimiter", + type=str, + default=',', + help="Csv delimiter", + ) + parser.add_argument( + "--entry_author_id", + type=str, + help="Id of entry author for StudentAcademicDisciplineLog", + ) + parser.add_argument( + "--changed_at", + type=str, + help="Date when StudentAcademicDisciplineLog changed_at", + ) + parser.add_argument( + "--trust_csv", + action="store_true", + default=False, + help="Apply all changes of mismatched fields automatically", + ) + + def get_branch(self, options) -> Branch: + branch_code = options["branch"] + if not branch_code: + available = Branch.objects.filter(branch__site_id=settings.SITE_ID).values_list("code", flat=True) + msg = f"Provide the code of the branch. Options: {available}" + raise CommandError(msg) + branch = Branch.objects.get(code=branch_code) + if input(f"Selected branch: {branch}. Is it right?\n" + f"y/[n]: ") != "y": + raise CommandError("Error asking for approval. Canceled") + return branch + + def get_partner(self, options) -> PartnerTag: + partner_slug = options["partner"] + if not partner_slug: + available = PartnerTag.objects.all().values_list("slug", flat=True) + msg = f"Provide the slug of the partner. Options: {available}" + raise CommandError(msg) + partner = PartnerTag.objects.get(slug=partner_slug) + if input(f"Selected partner: {partner}. Is it right?\n" + f"y/[n]: ") != "y": + raise CommandError("Error asking for approval. Canceled") + return partner + + def get_entry_author(self, options) -> User: + entry_author_id = options["entry_author_id"] + entry_author = User.objects.get(id=entry_author_id) + if input(f"Selected entry_author: {entry_author}. Is it right?\n" + f"y/[n]: ") != "y": + raise CommandError("Error asking for approval. Canceled") + return entry_author + + def get_gender_type(self, gender_str: str) -> str: + if gender_str == "Мужской": + return GenderTypes.MALE + elif gender_str == "Женский": + return GenderTypes.FEMALE + elif gender_str == "Другой": + return GenderTypes.OTHER + else: + assert not "possible" + + def handle(self, *args, **options): + branch = self.get_branch(options) + partner = self.get_partner(options) + entry_author = self.get_entry_author(options) + delimiter = options["delimiter"] + filename = options["filename"] + trust_csv = options["trust_csv"] + changed_at = datetime.strptime(options["changed_at"], "%d.%m.%Y") + with open(filename) as csvfile: + reader = csv.DictReader(csvfile, delimiter=delimiter) + headers = next(reader) + found_counter = 0 + created_counter = 0 + with transaction.atomic(): + for row in reader: + last_name, first_name, patronymic = row["Фамилия"], row["Имя"], row["Отчество"] + gender = self.get_gender_type(row["Пол"]) + gave_permission_at = None if not row["Дата подтверждения согласий (дата и время)"] \ + else datetime.strptime(row["Дата подтверждения согласий (дата и время)"], "%d.%m.%Y %H:%M:%S") + birth_date = datetime.strptime(row["Дата рождения"], "%d.%m.%Y").date() + phone, telegram_username, email = row["Номер телефона"], row["ТГ"].replace("@", ""), row["Почта"] + academic_discipline = AcademicDiscipline.objects.get(name=row["Направление"]) + try: + user = User.objects.get(email__iexact=email) + mismatched_fields = { + 'gender': (user.gender, gender), + 'birth_date': (user.birth_date, birth_date), + 'phone': (user.phone, phone), + 'telegram_username': (user.telegram_username, telegram_username), + } + mismatches = {field: (current, expected) + for field, (current, expected) in mismatched_fields.items() + if current != expected} + for field, (current, expected) in mismatches.items(): + if trust_csv or not current: + print(f"Changed field '{field}' of user '{user}' from '{current}' to '{expected}'") + setattr(user, field, expected) + user.save() + elif input(f"Change field '{field}' of user '{user}' from '{current}' to '{expected}'?\n" + f"y/[n]: ") == "y": + setattr(user, field, expected) + user.save() + found_counter += 1 + except User.DoesNotExist: + if not gave_permission_at: + try: + user = User.objects.get(last_name=last_name, first_name=first_name) + raise CommandError(f"STUDENT {last_name} {first_name} {patronymic} HAS ACCOUNT WITH EMAIL '" + f"{user.email}', but '{email}' was provided") + except User.DoesNotExist: + raise CommandError(f"NO PERMISSION FOR NEW STUDENT: {last_name} {first_name} {patronymic}") + user = create_account(username=generate_username_from_email(email), + password=User.objects.make_random_password(), + email=email, + gender=gender, + time_zone=branch.time_zone, + is_active=True, + first_name=first_name, + last_name=last_name, + patronymic=patronymic, + birth_date=birth_date, + telegram_username = telegram_username) + user.phone = phone + user.gave_permission_at = gave_permission_at + user.save() + created_counter += 1 + + profile_fields = { + "profile_type": StudentTypes.PARTNER, + "year_of_curriculum": timezone.now().year, + "user": user, + "branch": branch, + "year_of_admission": timezone.now().year, + "partner": partner + } + assert not StudentProfile.objects.filter( + user=user, + branch=branch, + type=StudentTypes.PARTNER, + partner=partner).exists() + student_profile = create_student_profile(**profile_fields) + student_profile.academic_disciplines.add(academic_discipline) + student_profile.save() + + log_entry = StudentAcademicDisciplineLog(academic_discipline=academic_discipline, + student_profile=student_profile, + entry_author=entry_author, + changed_at=changed_at) + log_entry.save() + + print(f"Found students: {found_counter}") + print(f"Created students: {created_counter}") + if input(f"Is everything ok?\n" + f"y/[n]: ") != "y": + raise CommandError("Error asking for approval. Canceled") diff --git a/apps/users/admin.py b/apps/users/admin.py index 063f8a010..091a7e3e6 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -20,7 +20,8 @@ from .import_export import UserRecordResource from .models import ( CertificateOfParticipation, OnlineCourseRecord, SHADCourseRecord, StudentProfile, - StudentStatusLog, StudentTypes, User, UserGroup, YandexUserData, StudentFieldLog, StudentAcademicDisciplineLog + StudentStatusLog, StudentTypes, User, UserGroup, YandexUserData, StudentFieldLog, StudentAcademicDisciplineLog, + PartnerTag ) from .services import assign_role, update_student_status, update_student_academic_discipline @@ -234,12 +235,10 @@ def get_readonly_fields(self, request, obj=None): def get_fieldsets(self, request, obj=None): fieldsets = [ (None, { - 'fields': ['type', 'is_paid_basis', 'new_track', 'branch', 'user', 'status', - 'university', 'partner', 'faculty', + 'fields': ['type', 'is_paid_basis', 'new_track', 'branch', 'user', 'university', 'partner', 'faculty', 'level_of_education_on_admission', 'level_of_education_on_admission_other', 'diploma_degree', 'graduation_year', - 'year_of_admission', 'year_of_curriculum', - 'academic_disciplines', 'invitation'] + 'year_of_admission', 'year_of_curriculum', 'invitation'] }), (_('Official Student Info'), { 'fields': ['is_official_student', 'birth_date', @@ -251,9 +250,13 @@ def get_fieldsets(self, request, obj=None): ] graduate_statuses = {StudentStatuses.GRADUATE, StudentStatuses.WILL_GRADUATE} - if obj and obj.status in graduate_statuses: + if obj: fields: list[str, ...] = fieldsets[0][1]['fields'] - fields.insert(fields.index('status'), 'graduate_without_diploma') + # Status and academic discipline logs work correctly only if object exists + fields.insert(fields.index('university'), 'status') + fields.insert(fields.index('invitation'), 'academic_disciplines') + if obj.status in graduate_statuses: + fields.insert(fields.index('status'), 'graduate_without_diploma') return fieldsets @@ -310,8 +313,12 @@ class CertificateOfParticipationAdmin(admin.ModelAdmin): list_display = ["student_profile", "created"] raw_id_fields = ["student_profile"] +class PartnerTagAdmin(admin.ModelAdmin): + list_display = ('name', 'slug') + admin.site.register(User, UserRecordResourceAdmin) admin.site.register(StudentProfile, StudentProfileAdmin) admin.site.register(CertificateOfParticipation, CertificateOfParticipationAdmin) admin.site.register(SHADCourseRecord, SHADCourseRecordAdmin) +admin.site.register(PartnerTag, PartnerTagAdmin) diff --git a/lms/jinja2/lms/user_profile/_tab_student_profiles.html b/lms/jinja2/lms/user_profile/_tab_student_profiles.html index f28528963..b931839a3 100644 --- a/lms/jinja2/lms/user_profile/_tab_student_profiles.html +++ b/lms/jinja2/lms/user_profile/_tab_student_profiles.html @@ -64,6 +64,10 @@