Some checks failed
continuous-integration/drone/push Build is failing
- Add backend/utils.py for URL management - Update serializers to use normalize_file_url() - Update views to use URL utils from env vars - Fix frontend components to use NEXT_PUBLIC_API_URL - Add new env vars: DJANGO_BACKEND_URL, DJANGO_MEDIA_BASE_URL - Replace all hardcoded localhost:8000 with configurable URLs
194 lines
7.7 KiB
Python
194 lines
7.7 KiB
Python
# coding: utf-8
|
||
from rest_framework import generics, viewsets, permissions, status
|
||
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
||
from rest_framework_simplejwt.views import TokenObtainPairView
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from django.shortcuts import get_object_or_404
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.utils.decorators import method_decorator
|
||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||
from django.contrib.auth import get_user_model
|
||
from backend.utils import normalize_file_url, build_media_url
|
||
|
||
from .models import Link, LinkGroup
|
||
from .serializers import (
|
||
RegisterSerializer,
|
||
UserSerializer,
|
||
LinkSerializer,
|
||
LinkGroupSerializer,
|
||
)
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
class RegisterView(generics.CreateAPIView):
|
||
queryset = User.objects.all()
|
||
permission_classes = [permissions.AllowAny]
|
||
serializer_class = RegisterSerializer
|
||
|
||
|
||
@method_decorator(csrf_exempt, name='dispatch')
|
||
class LoginView(TokenObtainPairView):
|
||
permission_classes = [permissions.AllowAny]
|
||
|
||
|
||
class LinkGroupViewSet(viewsets.ModelViewSet):
|
||
queryset = LinkGroup.objects.all()
|
||
serializer_class = LinkGroupSerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
||
|
||
def get_queryset(self):
|
||
return self.queryset.filter(owner=self.request.user).order_by('order')
|
||
|
||
def perform_create(self, serializer):
|
||
serializer.save(owner=self.request.user)
|
||
|
||
|
||
class LinkViewSet(viewsets.ModelViewSet):
|
||
queryset = Link.objects.all()
|
||
serializer_class = LinkSerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
||
|
||
def get_queryset(self):
|
||
return Link.objects.filter(owner=self.request.user).order_by('order')
|
||
|
||
def perform_create(self, serializer):
|
||
serializer.save(owner=self.request.user)
|
||
|
||
|
||
class UserProfileView(generics.RetrieveAPIView):
|
||
serializer_class = UserSerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
def get_object(self):
|
||
return self.request.user
|
||
|
||
|
||
class UserLinksListView(generics.ListAPIView):
|
||
serializer_class = LinkSerializer
|
||
permission_classes = [permissions.AllowAny]
|
||
def get_queryset(self):
|
||
username = self.kwargs['username']
|
||
return Link.objects.filter(owner__username=username).order_by('order')
|
||
|
||
|
||
class PublicUserGroupsView(APIView):
|
||
"""
|
||
GET /api/users/{username}/public/
|
||
"""
|
||
permission_classes = [permissions.AllowAny]
|
||
|
||
def get(self, request, username):
|
||
# 1. Ищем пользователя
|
||
user = get_object_or_404(User, username=username)
|
||
|
||
# 2. Получаем настройки дизайна пользователя
|
||
from customization.models import DesignSettings
|
||
try:
|
||
design_settings = DesignSettings.objects.get(user=user)
|
||
# Заменяем Docker URL на внешний для клиента
|
||
background_image_url = None
|
||
if design_settings.background_image:
|
||
background_image_url = request.build_absolute_uri(design_settings.background_image.url)
|
||
background_image_url = normalize_file_url(background_image_url)
|
||
|
||
design_data = {
|
||
'theme_color': design_settings.theme_color,
|
||
'background_image': background_image_url,
|
||
'dashboard_layout': design_settings.dashboard_layout,
|
||
'groups_default_expanded': design_settings.groups_default_expanded,
|
||
'show_group_icons': design_settings.show_group_icons,
|
||
'show_link_icons': design_settings.show_link_icons,
|
||
'dashboard_background_color': design_settings.dashboard_background_color,
|
||
'font_family': design_settings.font_family,
|
||
'header_text_color': getattr(design_settings, 'header_text_color', '#000000'),
|
||
'group_text_color': getattr(design_settings, 'group_text_color', '#333333'),
|
||
'link_text_color': getattr(design_settings, 'link_text_color', '#666666'),
|
||
'cover_overlay_enabled': getattr(design_settings, 'cover_overlay_enabled', False),
|
||
'cover_overlay_color': getattr(design_settings, 'cover_overlay_color', '#000000'),
|
||
'cover_overlay_opacity': getattr(design_settings, 'cover_overlay_opacity', 0.5),
|
||
}
|
||
except DesignSettings.DoesNotExist:
|
||
# Настройки по умолчанию
|
||
design_data = {
|
||
'theme_color': '#ffffff',
|
||
'background_image': None,
|
||
'dashboard_layout': 'list',
|
||
'groups_default_expanded': True,
|
||
'show_group_icons': True,
|
||
'show_link_icons': True,
|
||
'dashboard_background_color': '#f8f9fa',
|
||
'font_family': 'sans-serif',
|
||
}
|
||
|
||
# 3. Берём только публичные группы со ссылками
|
||
groups_qs = LinkGroup.objects.filter(
|
||
owner=user,
|
||
is_public=True # Показываем только публичные группы
|
||
).prefetch_related('links')
|
||
|
||
# Формируем URL аватара и обложки с заменой Docker URL
|
||
avatar_url = None
|
||
if user.avatar:
|
||
avatar_url = request.build_absolute_uri(user.avatar.url)
|
||
avatar_url = normalize_file_url(avatar_url)
|
||
|
||
cover_url = None
|
||
if user.cover:
|
||
cover_url = request.build_absolute_uri(user.cover.url)
|
||
cover_url = normalize_file_url(cover_url)
|
||
|
||
result = {
|
||
"username": user.username,
|
||
"full_name": user.full_name,
|
||
"bio": user.bio,
|
||
"avatar": avatar_url,
|
||
"cover": cover_url,
|
||
"design_settings": design_data,
|
||
"groups": []
|
||
}
|
||
|
||
for grp in groups_qs:
|
||
# icon у группы (абсолютный URL с заменой Docker URL)
|
||
grp_icon_url = None
|
||
if grp.icon:
|
||
grp_icon_url = request.build_absolute_uri(grp.icon.url)
|
||
grp_icon_url = normalize_file_url(grp_icon_url)
|
||
|
||
# background_image у группы
|
||
grp_bg_url = None
|
||
if grp.background_image:
|
||
grp_bg_url = request.build_absolute_uri(grp.background_image.url)
|
||
grp_bg_url = normalize_file_url(grp_bg_url)
|
||
|
||
grp_data = {
|
||
"id": grp.id,
|
||
"name": grp.name,
|
||
"description": grp.description,
|
||
"icon_url": grp_icon_url, # Используем icon_url для консистентности с API
|
||
"background_image": grp_bg_url,
|
||
"header_color": grp.header_color,
|
||
"is_favorite": grp.is_favorite,
|
||
"links": [],
|
||
}
|
||
|
||
for ln in grp.links.all():
|
||
# icon у ссылки с заменой Docker URL
|
||
ln_icon_url = None
|
||
if ln.icon:
|
||
ln_icon_url = request.build_absolute_uri(ln.icon.url)
|
||
ln_icon_url = normalize_file_url(ln_icon_url)
|
||
|
||
grp_data["links"].append({
|
||
"id": ln.id,
|
||
"title": ln.title,
|
||
"url": ln.url,
|
||
"icon_url": ln_icon_url, # Используем icon_url для консистентности
|
||
"description": ln.description,
|
||
})
|
||
|
||
result["groups"].append(grp_data)
|
||
|
||
return Response(result, status=status.HTTP_200_OK) |