From 09b45bd1d43ab8b524a4a0e5591df54eda7e7f9b Mon Sep 17 00:00:00 2001 From: Mike0001-droid Date: Tue, 4 Jun 2024 14:39:37 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/proj/account/__init__.py | 0 server/proj/account/admin.py | 3 + server/proj/account/apps.py | 6 ++ server/proj/account/forms.py | 60 +++++++++++ server/proj/account/migrations/__init__.py | 0 server/proj/account/models.py | 89 +++++++++++++++ server/proj/account/serializers.py | 35 ++++++ server/proj/account/tests.py | 3 + server/proj/account/urls.py | 12 +++ server/proj/account/views.py | 101 ++++++++++++++++++ .../settings/__pycache__/base.cpython-310.pyc | Bin 3779 -> 3014 bytes .../__pycache__/development.cpython-310.pyc | Bin 664 -> 731 bytes server/proj/conf/settings/base.py | 4 +- server/proj/conf/urls.py | 2 +- .../__pycache__/admin.cpython-310.pyc | Bin 212 -> 210 bytes .../loginApi/__pycache__/apps.cpython-310.pyc | Bin 454 -> 452 bytes .../__pycache__/models.cpython-310.pyc | Bin 209 -> 207 bytes .../news/__pycache__/__init__.cpython-310.pyc | Bin 167 -> 165 bytes .../news/__pycache__/admin.cpython-310.pyc | Bin 511 -> 509 bytes .../news/__pycache__/apps.cpython-310.pyc | Bin 442 -> 440 bytes .../news/__pycache__/models.cpython-310.pyc | Bin 584 -> 535 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 171 -> 169 bytes .../__pycache__/admin.cpython-310.pyc | Bin 501 -> 496 bytes .../rubricks/__pycache__/apps.cpython-310.pyc | Bin 454 -> 452 bytes .../__pycache__/models.cpython-310.pyc | Bin 510 -> 1085 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 174 -> 172 bytes .../__pycache__/admin.cpython-310.pyc | Bin 600 -> 773 bytes .../__pycache__/apps.cpython-310.pyc | Bin 463 -> 461 bytes .../__pycache__/models.cpython-310.pyc | Bin 600 -> 1438 bytes 29 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 server/proj/account/__init__.py create mode 100644 server/proj/account/admin.py create mode 100644 server/proj/account/apps.py create mode 100644 server/proj/account/forms.py create mode 100644 server/proj/account/migrations/__init__.py create mode 100644 server/proj/account/models.py create mode 100644 server/proj/account/serializers.py create mode 100644 server/proj/account/tests.py create mode 100644 server/proj/account/urls.py create mode 100644 server/proj/account/views.py diff --git a/server/proj/account/__init__.py b/server/proj/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/proj/account/admin.py b/server/proj/account/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/proj/account/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/proj/account/apps.py b/server/proj/account/apps.py new file mode 100644 index 0000000..2b08f1a --- /dev/null +++ b/server/proj/account/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'account' diff --git a/server/proj/account/forms.py b/server/proj/account/forms.py new file mode 100644 index 0000000..c65f721 --- /dev/null +++ b/server/proj/account/forms.py @@ -0,0 +1,60 @@ +from django import forms +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.core.exceptions import ValidationError + +from account.models import MyUser + + +class UserCreationForm(forms.ModelForm): + """A form for creating new users. Includes all the required + fields, plus a repeated password.""" + password1 = forms.CharField(label='Пароль', widget=forms.PasswordInput) + password2 = forms.CharField(label='Пароль ещё раз', widget=forms.PasswordInput) + + class Meta: + model = MyUser + fields = ('email',) + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise ValidationError("Пароли не совпадают") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class UserChangeForm(forms.ModelForm): + """A form for updating users. Includes all the fields on + the user, but replaces the password field with admin's + disabled password hash display field. + """ + password = ReadOnlyPasswordHashField( + label='Пароль', + help_text='Пароли не хранятся в открытом виде, поэтому нет возможности увидеть пароль этого пользователя, ' + 'но вы можете изменить пароль, воспользовавшись ' + 'этой формой.', + ) + + class Meta: + model = MyUser + fields = ('email', 'password', 'is_active', 'is_staff', 'is_superuser') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + password = self.fields.get('password') + if password: + password.help_text = password.help_text.format('../password/') + user_permissions = self.fields.get('user_permissions') + if user_permissions: + user_permissions.queryset = user_permissions.queryset.select_related('content_type') + diff --git a/server/proj/account/migrations/__init__.py b/server/proj/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/proj/account/models.py b/server/proj/account/models.py new file mode 100644 index 0000000..002554f --- /dev/null +++ b/server/proj/account/models.py @@ -0,0 +1,89 @@ +from django.db import models +from django.utils import timezone +from django.core.mail import send_mail +from django.contrib.auth.models import ( + BaseUserManager, AbstractBaseUser, PermissionsMixin +) + + +class MyUserManager(BaseUserManager): + def create_user(self, email, phone, password=None): + """ + Creates and saves a User with the given email, date of + birth and password. + """ + if not email: + raise ValueError('Users must have an email address') + + user = self.model( + email=self.normalize_email(email), + phone=phone + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None): + """ + Creates and saves a superuser with the given email, date of + birth and password. + """ + user = self.create_user( + email, + password=password, + ) + user.is_staff = True + user.is_superuser = True + user.set_password(password) + user.save(using=self._db) + return user + + +class MyUser(AbstractBaseUser, PermissionsMixin): + email = models.EmailField( + verbose_name='Емайл', + max_length=255, + unique=True, + ) + first_name = models.CharField('Имя', max_length=150, blank=True) + last_name = models.CharField('Фамилия', max_length=150, blank=True) + phone = models.CharField('Телефон', max_length=15, blank=True, null=True, unique=True) + is_active = models.BooleanField('Активный', help_text='Отметьте, если пользователь должен считаться активным. ' + 'Уберите эту отметку вместо удаления учётной записи.', default=True) + is_staff = models.BooleanField('Статус персонала', help_text='Отметьте, если пользователь может входить в ' + 'административную часть сайта.', default=False) + date_joined = models.DateTimeField('Дата регистрации', default=timezone.now) + organization = models.CharField('Название организаци', max_length=200, null=True) + INN_us = models.CharField('ИНН', max_length=12, null=True) + urid_adress = models.CharField('Юридический адрес', max_length=200, null=True) + number = models.CharField('Номер телефона', max_length=20, null=True) + + objects = MyUserManager() + + USERNAME_FIELD = 'email' + + def __str__(self): + return self.email + + def full_name(self): + return f'{self.first_name} {self.last_name}' if self.first_name and self.last_name else self.email + + def has_perm(self, perm, obj=None): + """Does the user have a specific permission?""" + # Simplest possible answer: Yes, always + return True + + def has_module_perms(self, app_label): + """Does the user have permissions to view the app `app_label`?""" + # Simplest possible answer: Yes, always + return True + + def email_user(self, subject, message, from_email=None, **kwargs): + """Send an email to this user.""" + send_mail(subject, message, from_email, [self.email], **kwargs) + + class Meta: + verbose_name = 'Пользователь' + verbose_name_plural = 'Пользователи' + diff --git a/server/proj/account/serializers.py b/server/proj/account/serializers.py new file mode 100644 index 0000000..932e20b --- /dev/null +++ b/server/proj/account/serializers.py @@ -0,0 +1,35 @@ +from rest_framework.serializers import ModelSerializer, CharField, SerializerMethodField, ListField, IntegerField +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from account.models import MyUser + + +class MyUserSerializer(ModelSerializer): + + def create(self, validated_data): + user = MyUser.objects.create_user(**validated_data) + return user + + class Meta: + model = MyUser + fields = ('id', 'email', 'password', + 'first_name', 'last_name', + 'phone', 'date_joined', + 'last_login', 'INN_us', + 'urid_adress', 'organization' + ) + extra_kwargs = { + 'password': {'write_only': True}, + } + + + +class MyTokenObtainPairSerializer(TokenObtainPairSerializer): + + @classmethod + def get_token(cls, user): + token = super().get_token(user) + token['email'] = user.email + token['first_name'] = user.first_name + token['last_name'] = user.last_name + + return token diff --git a/server/proj/account/tests.py b/server/proj/account/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/proj/account/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/proj/account/urls.py b/server/proj/account/urls.py new file mode 100644 index 0000000..441c761 --- /dev/null +++ b/server/proj/account/urls.py @@ -0,0 +1,12 @@ +from rest_framework import routers +from account import views + +router = routers.DefaultRouter() + +router.register(r'', views.MyUserViewSet, basename='users') + +app_name = 'account' +urlpatterns = router.urls + + + diff --git a/server/proj/account/views.py b/server/proj/account/views.py new file mode 100644 index 0000000..2f20787 --- /dev/null +++ b/server/proj/account/views.py @@ -0,0 +1,101 @@ +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework.decorators import action +from rest_framework import status +from django.core.mail import send_mail +from django.contrib.auth.hashers import make_password +from conf import settings +from account.serializers import MyUserSerializer, MyTokenObtainPairSerializer +from account.models import MyUser +PermissionClass = IsAuthenticated if not settings.DEBUG else AllowAny + +class MyTokenObtainPairView(TokenObtainPairView): + permission_classes = [AllowAny] + serializer_class = MyTokenObtainPairSerializer + + +class MyUserViewSet(ViewSet): + """ + list: + Авторизированный пользователь + create_user: + Создание пользователя + update_user: + Обновление пользователей + """ + + def list(self, request): + serializer = MyUserSerializer(request.user, many=False) + return Response(serializer.data, status=status.HTTP_200_OK) + + @action(detail=False, methods=['post']) + def create_user(self, request): + serializer = MyUserSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + else: + text_error = '' + error_dict = {} + for error in serializer.errors: + elm_error = serializer.errors.get(error) + if len(elm_error) > 0: + text_error += "{} \n".format(elm_error[0]) + error_dict.update({error: elm_error[0]}) + return Response({"detail": text_error, "error": error_dict}, status=status.HTTP_400_BAD_REQUEST) + + token_data = { + "email": request.data["email"], + "password": request.data["password"] + } + token_serializer = MyTokenObtainPairSerializer(data=token_data) + token_serializer.is_valid(raise_exception=True) + return Response(token_serializer.validated_data, status=status.HTTP_201_CREATED) + + @action(detail=False, methods=['post']) + def update_user(self, request): + if 'email' in request.data: + del request.data['email'] + if 'password' in request.data: + request.data['password'] = make_password(request.data['password']) + serializer = MyUserSerializer(request.user, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + + return Response(serializer.data) + + @action(detail=False, methods=['post']) + def password_reset_user(self, request): + if 'email' in request.data: + email = request.data.get('email') + try: + user = MyUser.objects.get(email=email) + password = MyUser.objects.make_random_password() + user.set_password(password) + user.save(update_fields=['password']) + message = "Запрос на восстановление пароля \nНовый пароль сгенерирован: {password}".format(password=password) + send_mail('Смена пароля', message, settings.EMAIL_HOST_USER, [email]) + return Response({'detail': 'Новый пароль отправлен на емайл: {}'.format(email)}, status=status.HTTP_200_OK) + except MyUser.DoesNotExist: + text = 'Пользователь с емайлом {}, не найден'.format(email) + return Response({'detail': text, 'error': {'email': text}}, status=status.HTTP_400_BAD_REQUEST) + except: + return Response( + {'detail': 'Произошла неизвестная ошибка', 'error': {'email': 'Произошла неизвестная ошибка'}}, + status=status.HTTP_400_BAD_REQUEST) + return Response({'detail': 'Не передан емайл', 'error': {'email': 'Не передан емайл'}}, + status=status.HTTP_400_BAD_REQUEST) + + def get_permissions(self): + if self.action == "list": + permission_classes = [IsAuthenticated] + elif self.action == "create_user": + permission_classes = [AllowAny] + elif self.action == "update_user": + permission_classes = [IsAuthenticated] + elif self.action == "password_reset_user": + permission_classes = [AllowAny] + else: + permission_classes = [IsAdminUser] + return [permission() for permission in permission_classes] diff --git a/server/proj/conf/settings/__pycache__/base.cpython-310.pyc b/server/proj/conf/settings/__pycache__/base.cpython-310.pyc index ed22f8221b8b8dd60ed3202ff1d53220022c21b8..1d0b78ff3737a59dfba035ffdbc12427006aa3dc 100644 GIT binary patch delta 219 zcmX>sdrVwApO=@50SH>|$EE!gVPJR+;vfSyAjbiSi;X5~zxCo|NKr^QG} z9Hp3|lA@Z<6s45HkfN5OoU0P0!pM-SlA@lX(aQ*=HB+>D8B?_9Fh!}R@If-7Y>OR?!=k;V&UIRv<$zi-& zYz9CF6d6oz<4t5NoBV<|N}h?yl<7YcGZPEj4<;TaMwWj(tQ^c7jBNi{SeckO{sRDd CXEW*m delta 966 zcmZ`#O-vI}5boP{w?BYUMDYiq{47wgRs6T2Vxbfgfkfhg_0VjY*JZ>0G;g;Awjn)x z;9&OJ#H;bj(Zqu$UOjj*d-Y_9(F+$7jkBeclHg0;do$n6H{Z;B>i&3T-0bLx1@Ls0 zSBh`W2Lj)Gr}ea>Glkds@M`==72?nj1270fAl8B~+_Xl}8m)1VKy(boX%wzr4nUIj z(Kw`PLIb;Y0(SivHi7&b3W1xba|`X;E%qJd#Zkadyj$a?ez=Dz_aO}rYC&lLCgGvN zNrMo8N7|4sN+JnR5gxAseoeJ( zlWnDt#kzeB!Q<=8@c30twk$7FM4G=DFM2SVO_>zY<;~2NO)aUdAg`{MOVeiAu$|N8 zl4i<~*D0v7U(&&_vF>++4z_=)F56VCdhM+YFWksxz1WEtnqQn>#5_ZXlqa|jR zWCi!i`p{=_d=h+pGnQ%^(gjd0xuDTJ-Ne~YJMV7<7a_%(16XVTSyCk22<^thp9vXt ze?^kSb9&n{wu`#QQ82|r!R<>nW2?#tK&8Ft^r#)K@!Sk~Q($>giM&?eC!M>zkI s&>v1X`a^$1`&=hS1dP8tpS!1@>n#>PM_(Wr8tfW$izC1>I5^xt$mJG`hktO$EtUZPpb$;QDrKM*lX)21 z7lQKkI_zu7Z?IXd_ZC)Ly-iCEisvoDML#Q$mC$)U}RxpV`O4wVnjfeUo8JQ QxaF7x7&(}D7&-m`0JBzC%K!iX delta 321 zcmcc3I)ha^pO=@50SG2UN2Yl(F)%y^agYHEkmCTv#dQ<473))3QaGd;QaI-@Wim#w zrf{XQrEuFYq_U?nWN|bzrtoC57FmPXDGZVf3pi7F7cv&xrZA-N<#6S4M{(!!MDZ{( zr0}QmqzC|Yai{V$GXnXH!Emf8_!4Bf-%CaysmXYYQ7fc~8OXfFo|2lDSZbA1QYDp= zm6(^Fua}afmz0>Cotl?YtXEu^lUb5#T*U+J6AvTD9{^QzMaKXD diff --git a/server/proj/conf/settings/base.py b/server/proj/conf/settings/base.py index aa8bb97..887a404 100644 --- a/server/proj/conf/settings/base.py +++ b/server/proj/conf/settings/base.py @@ -110,7 +110,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' ROTATE_LOG_SIZE = 15 * 1024 * 1024 ROTATE_LOG_COUNT = 10 -LOGGING = { +""" LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { @@ -159,7 +159,7 @@ LOGGING = { 'propagate': False, }, }, -} +} """ REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ diff --git a/server/proj/conf/urls.py b/server/proj/conf/urls.py index cee2d02..d9b9c7d 100644 --- a/server/proj/conf/urls.py +++ b/server/proj/conf/urls.py @@ -31,7 +31,7 @@ urlpatterns = [ path('api/', include_docs_urls(title='API docs')), path('api/radio/', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), - + path('api/user/', include('account.urls', namespace='user')), # path('webhook/', AzuraNowPlayingWebhookView.as_view(), name='webhook-receiver'), path('api/login/', login, name='login'), path('api/register/', register, name='register'), diff --git a/server/proj/loginApi/__pycache__/admin.cpython-310.pyc b/server/proj/loginApi/__pycache__/admin.cpython-310.pyc index 7893a029ded6349614bf64ccc182791f26e0f124..32e226adb3300101fff2b54198e223b37d2d481f 100644 GIT binary patch delta 79 zcmcb@c!`lapO=@50SG2UM^5CAT5_!Qk0mInIGdB5`;`UB_?O5=A}$rt^ok& Ck`k%_ diff --git a/server/proj/news/__pycache__/__init__.cpython-310.pyc b/server/proj/news/__pycache__/__init__.cpython-310.pyc index fa4d7cda0ef2ad6e5e1ae36630341f19316e13c7..2df7509e88a1d054f90e10547d6772cb10093be0 100644 GIT binary patch delta 79 zcmZ3^xRjAQpO=@50SG2UM^5Cnv2X=4ovmU*i&Kk=V=m0Pu0|0CI9oT;~5f^n39GV`Oxhti>oTmswJjn3905;{j!kog=%NWI;%DjMMAwv{rDsvWB z3S$Z@P>dVM2FWv~u=O%U@uaW^GiY+$V)08YFAmXUyv1Eml$o1YR2iS0S~>Bcumn(u zH7O@CFI$tThz)2}5j&9Z)8w7_R!fk_DKp)%v?SjxGc_mW7E@-*WHClhZefrr5b@P@ zau%bsTxLm8VoGLyjAuv?GVPR@oSmANGI=4RBnJ;rH3tI^kovmU*i&Kk=V=m0Pu0sua<9>f3u delta 81 zcmZ3M?s diff --git a/server/proj/rubricks/__pycache__/admin.cpython-310.pyc b/server/proj/rubricks/__pycache__/admin.cpython-310.pyc index ef109228d36557168c02234abb3717dc374368e8..69d33a2806d942d6ad233484e567b116140e7258 100644 GIT binary patch delta 237 zcmey${DE0JpO=@50SH#zjZ51Gq#uJg$bc2daRB0Ci;3FOGO3IUSW=l6GP*OQu%xiI zFr=`iGG?(hGdD9vu}w@%um`BS)qPQmX2%$)kh*HEw2+hgjboOqXbIv}qdxt_q5pX05 zlp2HtqJ=611)?C5zp!psqM{)`fEs4biDW@oY3BC3nc4YvW;Ur-T>|UF_Xn#zgODEx zCYJ?Z4n$rD#R#J@=~16jvOt)@%x%I>Xf;AYoi6c5}nd>c@KSAfbArcGVZP2l|^jQ2SR$Tce@+INAbms18nk zgAb5*d*Mb9^L{5?%dTPAO9=R+j?`C>J&?n{0;8WMAuGPs$l};Dg$*&<>I2Y4##h5E zPRHrrs$+F9ditOA*$vF~3J4zMoOab`wSO+r&0^FGJ6zuMOl`N~u)i*_u)35D;v{52 znzSk4vxR#DWFACf!&CBvbm@>nfoQ`sv?)^ogu@<(+gp;ytM7@Zg5$3R47=65FS(FS z^%|HRtKHGF(TmaZ=Hkk7$fBg#iBgeo!08be&4Ea|O_5i9T{g#qFgtjxi$MT+f&dvv z1}A1*y-W|3y$|^TH^M8}%(_zy-|B^642%JPorN_^Ef3U2S-_c7gzJ z7X;c30$`NIxUU4k!z_$XEfwJ2N+i!c^@T|k4B||LabA)Mg}JnaX|YxkTi_6jI#gVO zZNsaIY50m6RCQE%X&kW=_0CBMnkVs{SVoYI(VoGJsVo704Vd`a! zVg<6!J8KO8-Sc4ff*>14~l_nKsCWmM;-r_DO%FInHs*KN0t$g_( zXcSP4D>tzsJ|{IVy(Gg=lj{~oe0*MFZfbn|Ew1?Z-29Z%93Xr0KPJBVA|{}QTRcvg z>5io(`EHr1IVrc8GE;7GI%gynLAWd+t+zNsQY%Uz0^BL7#mPmP1tppJc`F%;1b`-h ziLahOma|n%XmM&$am=;2> KK>BzX#Fzo8*>Ujz diff --git a/server/proj/userProfile/__pycache__/__init__.cpython-310.pyc b/server/proj/userProfile/__pycache__/__init__.cpython-310.pyc index f30d03c020fb7d1c08cba5ab0ca1dcd76c324680..115d566aed93ab6dc1fe86baeeef4ca747f42ba8 100644 GIT binary patch delta 79 zcmZ3-xQ3BCpO=@50SG2UM^5CnvG4&hovmU*i&Kk=V=m0Pu1pq-i9?$>) delta 81 zcmZ3(xQ>xKpO=@50SLA#dQarGvGM~lovmU*i&Kk=V=m0Pu#lYFieGfpMAp!{cz6J6@#-6yHj8| z;#i>uQ;Z!BRM3Pp1RU~6MNLfOCZP%Vag9aD1_FN|BL0XUA)fHT8^j?pPtU?LOf#QL ze2(xGa_OJkkY|Ue7+yd}RA3udypb({Y^oQcec-$c;EIaC4xSi2lS%V)r@E7$MWU)EpOpEs{J?>2AC zgXzu^6|nY@EOjS)sau=Lre;fEtJ{fjz5a^XqU$bp6O0#m6KxjbYSq8&-E)q;EE7VJO<=G>b;f-q`?ropzzWWB9qno2V*ux|u;VrPY@h?4X#*F{~ delta 348 zcmZo=yTPKJ&&$ij00dhVz0*7x85kaeILLqn$Z-JT;(&?Tss@YYbwV=CZIZwUL7=A=$MC(l?s z@twL58_>X8TsfK9sVTwvdFjP|nv>NT&0Kha!bKqBt2dBvwu%WYPAw{qxiIU(z6<*= zY`EBTvHN0sOlC<@VoGLyjAuv?GVPR@oSmANGI>6um?S$;I}Z>SgIv$Sz`~GbKJPGc_lr_!gIMeoAVNV@hsj9$4Kimg3Bk)LR@ysp*-;C8!ct#&H delta 83 zcmX@he4d#*pO=@50SLA#dT->GV6^fAvYoABLW@(2ieoO!y0GuU{tFu}HeKw#*dCKv bQk0mInIGdB5`;`UB_?O5=A}%IW-I~#scs)J diff --git a/server/proj/userProfile/__pycache__/models.cpython-310.pyc b/server/proj/userProfile/__pycache__/models.cpython-310.pyc index 5fbb65d6460bf6e0a5051f07c55be8db69608592..6b277450e99fe30086796fd662fdd822936535fb 100644 GIT binary patch literal 1438 zcmZux&2QaA5a0F3d-jW88UiX1ffNo1T&WLQ-!U0in;{vB3(Gt~ChA_xn8p z>*qhO?QV7m`3r@`<$$mOZ}C15PB@h$r4gmbThdDH$Tqw!oz#t7O5P*f;qFbsU19Hc z4(+H5nhx)RraRYo+}k3d{|G#ikZR|nLMYffY0iZ#fb*|xZI0eegycJDv$!7T^D1Zs zi6|$L1%4f2;&FQ0in_w%7CbiaK6fCNtGgeld^eE-z2_N&K%x}m(9-U=x@qe>zlF!pg3%l1Us5L@>*GcnGAm_(I@y6*7O*6`BX?`WrjJ1I;y z>dEA~;9Ge%Eq)^E9H@($pJA4bMBJs!yU9pVr2?X(c0}ZbMSwxW2IcA8Y166Dh z>=?>j{s?{o4*8+~Z3(;hs?EfcK%!{X;8S;>&>hIqi%^oL0|6sQSD5r1@sO zIc#phJ-=*j)DP<8`uF(%{IGf%t-b~1 z?$`Ig+e7r)e7zXr7}OA=3flyJ1KVGkFTn4;`mTw8chP#D(D)!IA#0YT@l+JAg|_O# z=gI@3{c0xjn6on9p&*|wH_MBpO!5p6aS9=K|7#hw0*-Z;v7%Iry@H{z>(=N&fG7{<2TxQS zliNV+XMmhmoYTmL7pjaY9r(t>RZ+&(GFX{@JOP{@)W6n8qtI18Xchh|)DjRKKs#>d zg=kZMdQoCCS*chqRh^umM&`w%A=KkD(Z?{RVJeE(*aPqZ#6@O@E3XSix64KtA?N8<{tWtph2PB{Y JUA4}?@GpE&kyZcz delta 103 zcmbQoeS?KBpO=@50SFAO0@6fSCi2NJT20ipWJ_TSX3%7xSo5FJXL1drv|MIMQDRDF uevD^G5HjtQn4F!Omoj-Xqdv2rCdXtE*3ij$tV%o_KofZwco=yY#h3tY0~xmf