@@ -1,12 +1,14 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from common.models.baseModels import BaseModel
|
from common.models.baseModels import BaseModel
|
||||||
|
from common.models.choiceModels import InvitationStatus
|
||||||
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
class Project(BaseModel):
|
class Project(BaseModel):
|
||||||
title = models.CharField(max_length=20)
|
title = models.CharField(max_length=20)
|
||||||
is_team = models.BooleanField(default=False)
|
is_team = models.BooleanField(default=False)
|
||||||
@@ -24,5 +26,12 @@ class Project(BaseModel):
|
|||||||
scrappers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='scrapped_projects', blank=True)
|
scrappers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='scrapped_projects', blank=True)
|
||||||
|
|
||||||
class ProjectTeamList(BaseModel):
|
class ProjectTeamList(BaseModel):
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='project_team_list', to_field='id')
|
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='team_project_member_list', to_field='id')
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='project_team_list',to_field='id')
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='team_project_list',to_field='id')
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectInvitation(BaseModel):
|
||||||
|
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_invitations')
|
||||||
|
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_invitations')
|
||||||
|
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||||
|
status = models.CharField(max_length=10, choices=InvitationStatus.choices)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from common.utils.codeManger import set_expire
|
|||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
||||||
|
|
||||||
|
from common.models.choiceModels import NotificationType
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
def create_user(self, email, password, **kwargs):
|
def create_user(self, email, password, **kwargs):
|
||||||
user = self.model(email = email, **kwargs)
|
user = self.model(email = email, **kwargs)
|
||||||
@@ -47,6 +49,7 @@ class User(BaseModel, AbstractBaseUser, PermissionsMixin):
|
|||||||
external_links = ArrayField(models.TextField(), default=list, blank=True)
|
external_links = ArrayField(models.TextField(), default=list, blank=True)
|
||||||
short_bio = models.CharField(max_length=100, blank=True)
|
short_bio = models.CharField(max_length=100, blank=True)
|
||||||
profile_image = models.ImageField(upload_to='', blank=True)
|
profile_image = models.ImageField(upload_to='', blank=True)
|
||||||
|
banner_image = models.ImageField(upload_to='', blank=True)
|
||||||
|
|
||||||
is_staff = models.BooleanField(default=False)
|
is_staff = models.BooleanField(default=False)
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
@@ -58,4 +61,10 @@ class User(BaseModel, AbstractBaseUser, PermissionsMixin):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.nickname
|
return self.nickname
|
||||||
|
|
||||||
|
|
||||||
|
class Notification(BaseModel):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications')
|
||||||
|
content = models.TextField()
|
||||||
|
is_read = models.BooleanField(default=False)
|
||||||
|
note_type = models.CharField(max_length=10, choices=NotificationType.choices)
|
||||||
|
|||||||
@@ -30,4 +30,40 @@ class SetPortofolioRequiredInfoSerializer(serializers.ModelSerializer):
|
|||||||
class TagUserSerializer(serializers.ModelSerializer):
|
class TagUserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['nickname', 'profile_image']
|
fields = ['nickname', 'profile_image']
|
||||||
|
|
||||||
|
class UserProfileSerializer(serializers.ModelSerializer):
|
||||||
|
represent_portfolio_id = serializers.CharField(read_only=True)
|
||||||
|
new_notification_count = serializers.IntegerField(read_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
'banner_image',
|
||||||
|
'profile_image',
|
||||||
|
'nickname',
|
||||||
|
'external_links',
|
||||||
|
'job_and_interests',
|
||||||
|
'skills',
|
||||||
|
'short_bio',
|
||||||
|
'represent_portfolio_id',
|
||||||
|
'new_notification_count'
|
||||||
|
]
|
||||||
|
|
||||||
|
class UserMemberInfoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
'realname',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'nickname',
|
||||||
|
'gender',
|
||||||
|
'birth_date',
|
||||||
|
'custom_url',
|
||||||
|
'job_and_interests'
|
||||||
|
]
|
||||||
|
|
||||||
|
class NotificationSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Notification
|
||||||
|
fields = ['id', 'content', 'note_type', 'is_read']
|
||||||
85
users/services.py
Normal file
85
users/services.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from .models import *
|
||||||
|
from projects.models import *
|
||||||
|
from portfolios.models import *
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
# 30일 이전 일수 계산
|
||||||
|
thirty_days_ago = timezone.now() - timedelta(days=30)
|
||||||
|
|
||||||
|
class CheckUserFieldDuplicateService:
|
||||||
|
@staticmethod
|
||||||
|
def check_nickname_duplicate(query):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_custom_url_duplicate():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 알림 관련 서비스 로직
|
||||||
|
class NotifiationService:
|
||||||
|
@staticmethod
|
||||||
|
def set_content_by_type():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserToNotificationService:
|
||||||
|
@staticmethod
|
||||||
|
def get_new_notification_count(user: User):
|
||||||
|
return user.notifications.filter(created_at__lt=thirty_days_ago, is_read=False).count()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all_notification(user: User):
|
||||||
|
return user.notifications.filter(created_at__lt=thirty_days_ago)
|
||||||
|
|
||||||
|
|
||||||
|
# 유저 -> 포트폴리오 관련 서비스 로직
|
||||||
|
class UserToPortfolioService:
|
||||||
|
@staticmethod
|
||||||
|
def get_represent_portfolio(user: User):
|
||||||
|
if represent_portfolio := user.owned_portfolios.filter(is_represent=True).first():
|
||||||
|
return represent_portfolio.id
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_published_portfolio(user: User):
|
||||||
|
return user.owned_portfolios.filter(is_published=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_unpublished_portfolio(user: User):
|
||||||
|
return user.owned_portfolios.filter(is_published=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_scrap_portfolio(user: User):
|
||||||
|
return user.scrapped_portfolios.filter(is_published=True)
|
||||||
|
|
||||||
|
# 유저 -> 프로젝트 관련 서비스 로직
|
||||||
|
class UserToProjectService:
|
||||||
|
@staticmethod
|
||||||
|
def get_published_solo_project(user: User):
|
||||||
|
return user.owned_projects.filter(is_team=False, is_published=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_unpublished_solo_project(user: User):
|
||||||
|
return user.owned_projects.filter(is_team=False, is_published=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_published_team_project(user: User):
|
||||||
|
return Project.objects.filter(
|
||||||
|
team_project_member_list__user = user,
|
||||||
|
is_published=True
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_unpublished_team_project(user: User):
|
||||||
|
return Project.objects.filter(
|
||||||
|
team_project_member_list__user = user,
|
||||||
|
is_published=False
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_scrap_project(user: User):
|
||||||
|
return user.scrapped_projects.filter(is_published=True)
|
||||||
|
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
|
|
||||||
from .views import *
|
from .views import *
|
||||||
|
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
app_name = 'users'
|
app_name = 'users'
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'notifications', NotificationReadViewSet, basename='notification')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('refresh-token/', RefreshAPIView.as_view()),
|
path('refresh-token/', RefreshAPIView.as_view()),
|
||||||
path('join/', JoinAPIView.as_view()),
|
path('join/', JoinAPIView.as_view()),
|
||||||
@@ -12,4 +17,8 @@ urlpatterns = [
|
|||||||
path('portfolio-info/', SetPortofolioRequiredInfoAPIView.as_view()),
|
path('portfolio-info/', SetPortofolioRequiredInfoAPIView.as_view()),
|
||||||
path('portfolio-info/check/', SetPortofolioRequiredInfoAPIView.as_view()),
|
path('portfolio-info/check/', SetPortofolioRequiredInfoAPIView.as_view()),
|
||||||
path('teamtag/', TagUserAPIView.as_view()),
|
path('teamtag/', TagUserAPIView.as_view()),
|
||||||
|
path('mypage/profile/<str:nickname>/', MyPageProfileAPIView.as_view()),
|
||||||
|
path('mypage/works/<str:nickname>/', MyPageWorkListAPIView.as_view()),
|
||||||
|
path('mypage/my-info/', MyPageMemberInfoAPIView.as_view()),
|
||||||
|
path('', include(router.urls)),
|
||||||
]
|
]
|
||||||
138
users/views.py
138
users/views.py
@@ -3,6 +3,7 @@ from django.conf import settings
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, TokenRefreshSerializer
|
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, TokenRefreshSerializer
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
|
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
|
||||||
@@ -16,11 +17,18 @@ from django.db.models import Case, When, Value, IntegerField, Q
|
|||||||
|
|
||||||
from .models import *
|
from .models import *
|
||||||
from .serializers import *
|
from .serializers import *
|
||||||
|
from .services import *
|
||||||
|
|
||||||
|
from projects.serializers import *
|
||||||
|
from portfolios.serializers import *
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
|
||||||
class RefreshAPIView(APIView):
|
class RefreshAPIView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
# access token 재발급
|
# access token 재발급
|
||||||
|
@transaction.atomic
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
refresh = request.COOKIES.get("refresh")
|
refresh = request.COOKIES.get("refresh")
|
||||||
if not refresh:
|
if not refresh:
|
||||||
@@ -41,6 +49,7 @@ class RefreshAPIView(APIView):
|
|||||||
class JoinAPIView(APIView):
|
class JoinAPIView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
# 회원가입
|
# 회원가입
|
||||||
|
@transaction.atomic
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = JoinSerializer(data=request.data)
|
serializer = JoinSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
@@ -52,6 +61,7 @@ class JoinAPIView(APIView):
|
|||||||
class LoginAPIView(APIView):
|
class LoginAPIView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
# 로그인
|
# 로그인
|
||||||
|
@transaction.atomic
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
email=request.data.get("email", None)
|
email=request.data.get("email", None)
|
||||||
password=request.data.get("password", None)
|
password=request.data.get("password", None)
|
||||||
@@ -122,6 +132,7 @@ class SetPortofolioRequiredInfoAPIView(APIView):
|
|||||||
else:
|
else:
|
||||||
return Response({"message": "can use this url"}, status=status.HTTP_200_OK)
|
return Response({"message": "can use this url"}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
user = request.user
|
user = request.user
|
||||||
serializer = SetPortofolioRequiredInfoSerializer(user, data=request.data)
|
serializer = SetPortofolioRequiredInfoSerializer(user, data=request.data)
|
||||||
@@ -130,4 +141,129 @@ class SetPortofolioRequiredInfoAPIView(APIView):
|
|||||||
user.is_custom_url = True
|
user.is_custom_url = True
|
||||||
user.save()
|
user.save()
|
||||||
return Response({"message": "updated successfully"}, status=status.HTTP_202_ACCEPTED)
|
return Response({"message": "updated successfully"}, status=status.HTTP_202_ACCEPTED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 마이페이지 관련
|
||||||
|
class MyPageProfileAPIView(APIView):
|
||||||
|
|
||||||
|
# 프로필 조회(안 읽은 알림 수)
|
||||||
|
def get(self, request, nickname):
|
||||||
|
target_user = get_object_or_404(User, nickname=nickname)
|
||||||
|
user = request.user
|
||||||
|
serializer = UserProfileSerializer(target_user)
|
||||||
|
data = serializer.data
|
||||||
|
data['represent_portfolio_id'] = UserToPortfolioService.get_represent_portfolio(target_user)
|
||||||
|
data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user)
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
# 프로필 수정
|
||||||
|
@transaction.atomic
|
||||||
|
def patch(self, request, nickname):
|
||||||
|
target_user = get_object_or_404(User, nickname=nickname)
|
||||||
|
user = request.user
|
||||||
|
if user == target_user:
|
||||||
|
serializer = UserProfileSerializer(user, request.data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
data = serializer.data
|
||||||
|
data['represent_portfolio_id'] = UserToPortfolioService.get_represent_portfolio(target_user)
|
||||||
|
data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user)
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
return Response({"message": "Not account owner"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class MyPageWorkListAPIView(APIView):
|
||||||
|
|
||||||
|
# 포폴, 플젝, 작업중, 스크랩 조회(안 읽은 알림 수)
|
||||||
|
def get(self, request, nickname):
|
||||||
|
target_user = get_object_or_404(User, nickname=nickname)
|
||||||
|
user = request.user
|
||||||
|
retreive_type = request.query_params.get('type')
|
||||||
|
data = {}
|
||||||
|
if retreive_type == 'portfolio':
|
||||||
|
portfolios = UserToPortfolioService.get_published_portfolio(target_user)
|
||||||
|
serializer = PortfolioListViewSerializer(portfolios, many=True)
|
||||||
|
data = {
|
||||||
|
"portfolios": serializer.data
|
||||||
|
}
|
||||||
|
elif retreive_type == 'project':
|
||||||
|
solo_projects = UserToProjectService.get_published_solo_project(target_user)
|
||||||
|
team_projects = UserToProjectService.get_published_team_project(target_user)
|
||||||
|
sp_serializer = ProjectListViewSerializer(solo_projects, many=True)
|
||||||
|
tp_serializer = ProjectListViewSerializer(team_projects, many=True)
|
||||||
|
data = {
|
||||||
|
"solo_projects": sp_serializer.data,
|
||||||
|
"team_projects": tp_serializer.data,
|
||||||
|
}
|
||||||
|
elif retreive_type == 'working':
|
||||||
|
if user != target_user:
|
||||||
|
return Response({"message": "Not account owner"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
portfolios = UserToPortfolioService.get_unpublished_portfolio(target_user)
|
||||||
|
solo_projects = UserToProjectService.get_unpublished_solo_project(target_user)
|
||||||
|
team_projects = UserToProjectService.get_unpublished_team_project(target_user)
|
||||||
|
po_serializer = PortfolioListViewSerializer(portfolios, many=True)
|
||||||
|
sp_serializer = ProjectListViewSerializer(solo_projects, many=True)
|
||||||
|
tp_serializer = ProjectListViewSerializer(team_projects, many=True)
|
||||||
|
data = {
|
||||||
|
"portfolios": po_serializer.data,
|
||||||
|
"solo_projects": sp_serializer.data,
|
||||||
|
"team_projects": tp_serializer.data,
|
||||||
|
}
|
||||||
|
elif retreive_type == 'scrap':
|
||||||
|
if user != target_user:
|
||||||
|
return Response({"message": "Not account owner"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
portfolios = UserToPortfolioService.get_scrap_portfolio(target_user)
|
||||||
|
po_serializer = PortfolioListViewSerializer(portfolios, many=True)
|
||||||
|
projects = UserToProjectService.get_scrap_project(target_user)
|
||||||
|
pr_serializer = ProjectListViewSerializer(projects, many=True)
|
||||||
|
data = {
|
||||||
|
"portfolios": po_serializer.data,
|
||||||
|
"projects": pr_serializer.data,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return Response({"message": "not allowed retreive_type"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user)
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
class MyPageMemberInfoAPIView(APIView):
|
||||||
|
|
||||||
|
# 내 정보 조회(안 읽은 알림 수)
|
||||||
|
def get(self, request):
|
||||||
|
user = request.user
|
||||||
|
serializer = UserMemberInfoSerializer(user)
|
||||||
|
data = serializer.data
|
||||||
|
data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user)
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
# 내 정보 수정
|
||||||
|
@transaction.atomic
|
||||||
|
def patch(self, request):
|
||||||
|
user = request.user
|
||||||
|
serializer = UserMemberInfoSerializer(user, request.data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class NotificationReadViewSet(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = NotificationSerializer
|
||||||
|
|
||||||
|
# 30일 이전 알림만 가져옴
|
||||||
|
def get_queryset(self):
|
||||||
|
return UserToNotificationService.get_all_notification(self.request.user)
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
instance.is_read = True
|
||||||
|
instance.save()
|
||||||
|
serializer = self.get_serializer(instance)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
Reference in New Issue
Block a user