Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions users/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ class CustomUserAdmin(admin.ModelAdmin):
"Важные даты",
{"fields": ("last_login", "date_joined")},
),
(
"Студенты мосполитеха",
{"fields": ("is_mospolytech_student", "study_group")},
),
)

list_display = (
Expand Down Expand Up @@ -296,10 +300,7 @@ def get_export_users_emails(self, users):
users = (
CustomUser.objects.all()
.select_related("v2_speciality")
.filter(
birthday__lte=date_limit_18,
birthday__gte=date_limit_22
)
.filter(birthday__lte=date_limit_18, birthday__gte=date_limit_22)
)
# little_mans = users.filter(birthday__lte=date_limit_18)
# big_mans = users.exclude(id__in=little_mans.values_list("id", flat=True))
Expand All @@ -312,13 +313,9 @@ def get_export_users_emails(self, users):
response_data.append(
[
user.first_name + " " + user.last_name,
(today.year - user.birthday.year)
if user.birthday.year
else None,
(today.year - user.birthday.year) if user.birthday.year else None,
user.city,
user.v2_speciality
if user.v2_speciality
else user.speciality,
user.v2_speciality if user.v2_speciality else user.speciality,
user.email,
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.11 on 2025-07-02 08:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("users", "0052_remove_customuser_organization"),
]

operations = [
migrations.AddField(
model_name="customuser",
name="is_mospolytech_student",
field=models.BooleanField(
default=False,
help_text="Флаг, указывающий, является ли пользователь студентом МосПолитеха",
verbose_name="Студент Московского Политеха",
),
),
migrations.AddField(
model_name="customuser",
name="study_group",
field=models.CharField(
blank=True,
help_text="Краткое обозначение учебной группы (до 10 символов)",
max_length=10,
null=True,
verbose_name="Учебная группа",
),
),
]
65 changes: 40 additions & 25 deletions users/models.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
from django.contrib.auth.models import AbstractUser
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import QuerySet
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.contrib.auth.models import AbstractUser
from django.contrib.contenttypes.fields import GenericRelation

from django_stubs_ext.db.models import TypedModelMeta

from users import constants
from users.managers import (
CustomUserManager,
UserAchievementManager,
LikesOnProjectManager,
UserAchievementManager,
)
from users.utils import normalize_user_phone
from users.validators import (
user_birthday_validator,
user_name_validator,
user_experience_years_range_validator,
user_name_validator,
user_phone_number_validation,
)
from users.utils import normalize_user_phone


def get_default_user_type():
Expand Down Expand Up @@ -102,7 +101,7 @@ class CustomUser(AbstractUser):
null=True,
blank=True,
verbose_name="Номер телефона",
help_text="Пример: +7 XXX XX-XX-XX | +7XXXXXXXXX | +7 (XXX) XX-XX-XX"
help_text="Пример: +7 XXX XX-XX-XX | +7XXXXXXXXX | +7 (XXX) XX-XX-XX",
)
v2_speciality = models.ForeignKey(
on_delete=models.SET_NULL,
Expand Down Expand Up @@ -138,7 +137,19 @@ class CustomUser(AbstractUser):
blank=True,
default=False,
verbose_name="Временная мера для переноса навыка",
help_text="Yes если оба поля `v2_speciality` и `skills` есть, No если поля не перенеслись"
help_text="Yes если оба поля `v2_speciality` и `skills` есть, No если поля не перенеслись",
)
is_mospolytech_student = models.BooleanField(
default=False,
verbose_name="Студент Московского Политеха",
help_text="Флаг, указывающий, является ли пользователь студентом МосПолитеха",
)
study_group = models.CharField(
max_length=10,
null=True,
blank=True,
verbose_name="Учебная группа",
help_text="Краткое обозначение учебной группы (до 10 символов)",
)

USERNAME_FIELD = "email"
Expand Down Expand Up @@ -181,7 +192,9 @@ def calculate_ordering_score(self) -> int:
def get_project_chats(self) -> QuerySet:
from chats.models import ProjectChat

user_project_ids = self.collaborations.all().values_list("project_id", flat=True)
user_project_ids = self.collaborations.all().values_list(
"project_id", flat=True
)
return ProjectChat.objects.filter(project__in=user_project_ids)

def get_full_name(self) -> str:
Expand All @@ -192,7 +205,11 @@ def get_user_age(self) -> int:
return None
today = timezone.now()
birthday = self.birthday
return today.year - birthday.year - ((today.month, today.day) < (birthday.month, birthday.day))
return (
today.year
- birthday.year
- ((today.month, today.day) < (birthday.month, birthday.day))
)

def __str__(self) -> str:
return f"User<{self.id}> - {self.first_name} {self.last_name}"
Expand Down Expand Up @@ -442,6 +459,7 @@ class Meta(TypedModelMeta):

class AbstractUserExperience(models.Model):
"""Abstact help model for user work|education experience."""

organization_name = models.CharField(
max_length=255,
verbose_name="Наименование организации",
Expand Down Expand Up @@ -469,9 +487,7 @@ class Meta:
abstract = True

def __str__(self) -> str:
return (
f"id: {self.id} - ({self.user.first_name} {self.user.last_name} user_id: {self.user.id})"
)
return f"id: {self.id} - ({self.user.first_name} {self.user.last_name} user_id: {self.user.id})"

def clean(self) -> None:
"""Validate both years `entry` <`completion`"""
Expand Down Expand Up @@ -541,6 +557,7 @@ class UserWorkExperience(AbstractUserExperience):
entry_year: PositiveSmallIntegerField Year of admission.
completion_year: PositiveSmallIntegerField Year of dismissal.
"""

user = models.ForeignKey(
to=CustomUser,
on_delete=models.CASCADE,
Expand Down Expand Up @@ -570,6 +587,7 @@ class UserLanguages(models.Model):
language: CharField(choise) languages.
language_level: CharField(choise) language level.
"""

user = models.ForeignKey(
to=CustomUser,
on_delete=models.CASCADE,
Expand Down Expand Up @@ -604,17 +622,17 @@ def clean(self) -> None:
"""
super().clean()
user_languages = self.user.user_languages.values_list("language", flat=True)
if (self.language not in user_languages) and len(user_languages) == constants.USER_MAX_LANGUAGES_COUNT:
if (self.language not in user_languages) and len(
user_languages
) == constants.USER_MAX_LANGUAGES_COUNT:
raise ValidationError(constants.COUNT_LANGUAGES_VALIDATION_MESSAGE)

def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)

def __str__(self) -> str:
return (
f"id: {self.id} - ({self.user.first_name} {self.user.last_name} user_id: {self.user.id})"
)
return f"id: {self.id} - ({self.user.first_name} {self.user.last_name} user_id: {self.user.id})"


class UserSkillConfirmation(models.Model):
Expand All @@ -626,23 +644,20 @@ class UserSkillConfirmation(models.Model):
confirmed_by: FK CustomUser.
confirmed_at: DateTimeField.
"""

skill_to_object = models.ForeignKey(
"core.SkillToObject",
on_delete=models.CASCADE,
related_name="confirmations"
"core.SkillToObject", on_delete=models.CASCADE, related_name="confirmations"
)
confirmed_by = models.ForeignKey(
CustomUser,
on_delete=models.CASCADE,
related_name="skill_confirmations"
CustomUser, on_delete=models.CASCADE, related_name="skill_confirmations"
)
confirmed_at = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["skill_to_object", "confirmed_by"],
name="unique_skill_confirmed_by"
name="unique_skill_confirmed_by",
)
]
verbose_name = "Подтверждение навыка"
Expand Down
21 changes: 11 additions & 10 deletions users/serializers.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
from typing import Any

from django.db import transaction
from django.contrib.contenttypes.models import ContentType
from django.forms.models import model_to_dict
from django.core.cache import cache
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
from django.forms.models import model_to_dict
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

from core.models import Skill, SkillToObject, Specialization, SpecializationCategory
from core.serializers import SkillToObjectSerializer
from core.models import SpecializationCategory, Specialization, Skill, SkillToObject
from core.services import get_views_count
from core.utils import get_user_online_cache_key
from partner_programs.models import PartnerProgram, PartnerProgramUserProfile
from projects.models import Project, Collaborator
from projects.models import Collaborator, Project
from projects.validators import validate_project
from users import constants
from users.utils import normalize_user_phone
from users.validators import specialization_exists_validator
from users.models import (
CustomUser,
Expert,
Expand All @@ -26,12 +25,12 @@
Mentor,
UserAchievement,
UserEducation,
UserWorkExperience,
UserSkillConfirmation,
UserLanguages,
UserSkillConfirmation,
UserWorkExperience,
)

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from users.utils import normalize_user_phone
from users.validators import specialization_exists_validator


class AchievementListSerializer(serializers.ModelSerializer[UserAchievement]):
Expand Down Expand Up @@ -451,6 +450,8 @@ class Meta:
"projects",
"programs",
"dataset_migration_applied",
"is_mospolytech_student", # новое булево поле
"study_group",
]

@transaction.atomic
Expand Down