Files
tripwithbonus/backend/api/account/client/views.py
2025-05-30 18:13:52 +03:00

408 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from rest_framework import status
from rest_framework.viewsets import ViewSet
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from datetime import timedelta
from rest_framework.exceptions import PermissionDenied
from django.utils.timezone import now as timezone_now
from .serializers import RouteSerializer, CreateRouteSerializer, CitySerializer, CountrySerializer, PlanChangeSerializer, PricingSerializer, LeadSerializer, LeadResponseSerializer
from api.auth.serializers import UserResponseSerializer
from api.models import UserProfile
from routes.models import Route, City, Country, Leads
from sitemanagement.models import Pricing, RoutePromotionLog, Transactions
from api.utils.decorators import handle_exceptions
from api.utils.emailSender import send_email
from api.utils.permissionChecker import check_monthly_limit
class UserDataView(ViewSet):
"""Эндпоинт для наполнения стора фронта данными"""
permission_classes = [IsAuthenticated]
def initial(self, request, *args, **kwargs):
try:
super().initial(request, *args, **kwargs)
except Exception as e:
print(f"Authentication error: {e}")
raise
@action(detail=False, methods=['get'])
@handle_exceptions
def user_data(self, request):
user = request.user
try:
user_data = UserResponseSerializer(user).data
return Response(user_data, status=status.HTTP_200_OK)
except UserProfile.DoesNotExist:
return Response(
{"error": "User profile not found"},
status=status.HTTP_404_NOT_FOUND
)
class AccountActionsView(ViewSet):
"""Действия в аккаунте пользователя:
- PATCH данных в account/main
- POST новых заявок"""
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['patch'])
@handle_exceptions
def change_data_main_tab(self, request):
"""Обновление данных на главной странице аккаунта"""
user = request.user
user_profile = get_object_or_404(UserProfile, user=user)
# обновляем данные пользователя
if 'firstName' in request.data:
user.first_name = request.data['firstName']
if 'lastName' in request.data:
user.last_name = request.data['lastName']
if 'email' in request.data:
email = request.data['email']
validate_email(email) # handle_exceptions обработает ValidationError
user.email = email
user.username = email
# обновляем номер телефона
if 'phone_number' in request.data:
phone = request.data['phone_number']
if phone:
if len(phone) < 13: # +375XXXXXXXXX
raise ValidationError("Номер телефона слишком короткий")
if len(phone) > 18:
raise ValidationError("Номер телефона слишком длинный")
# проверка на уникальность
if UserProfile.objects.filter(phone_number=phone).exclude(user=user).exists():
raise ValidationError("Этот номер телефона уже используется")
user_profile.phone_number = phone
# сохраняем изменения
user.save()
user_profile.save()
return Response({
"message": "Данные успешно обновлены",
"user": UserResponseSerializer(user).data
}, status=status.HTTP_200_OK)
@action(detail=False, methods=['get'])
@handle_exceptions
def user_routes(self, request):
"""Получаем список заявок юзера"""
user = request.user
routes = Route.objects.filter(owner=user)
return Response(RouteSerializer(routes, many=True).data, status=status.HTTP_200_OK)
@action(detail=False, methods=['post'])
@handle_exceptions
def create_route(self, request):
"""Создаем новую заявку"""
serializer = CreateRouteSerializer(data=request.data, context={'request': request})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
route = serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class CityView(ViewSet):
"""Получаем список городов из базы для автокомплита"""
@action(detail=False, methods=['get'])
@handle_exceptions
def get_cities(self, request):
# получаем параметр country_id из query params
country_id = request.query_params.get('country_id')
# базовый QuerySet
cities = City.objects.all()
# фильтруем города по стране, если указан country_id
if country_id:
cities = cities.filter(country_id=country_id)
# поиск по названию города
search = request.query_params.get('search')
if search:
cities = cities.filter(
Q(name__icontains=search) | # поиск по английскому названию
Q(russian_name__icontains=search) # поиск по русскому названию
)
# ограничиваем количество результатов и сортируем по имени
cities = cities.order_by('name')[:100]
return Response(CitySerializer(cities, many=True).data, status=status.HTTP_200_OK)
class CountryView(ViewSet):
"""Получаем список стран из базы для автокомплита"""
@action(detail=False, methods=['get'])
@handle_exceptions
def get_countries(self, request):
# базовый QuerySet
countries = Country.objects.all()
# поиск по названию страны
search = request.query_params.get('search')
if search:
countries = countries.filter(
models.Q(international_name__icontains=search) |
models.Q(official_name__icontains=search)
)
# сортируем по международному названию
countries = countries.order_by('international_name')
return Response(CountrySerializer(countries, many=True).data, status=status.HTTP_200_OK)
class ChangeUserMembership(ViewSet):
"""Меняем тарифный план пользователя"""
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['patch'])
@handle_exceptions
def change_plan(self, request):
"""Меняем пользователю тарифный план"""
user = request.user
user_profile = get_object_or_404(UserProfile, user=user)
# преобразуем plan в account_type
if 'plan' in request.data and 'account_type' not in request.data:
request.data['account_type'] = request.data['plan']
if 'account_type' not in request.data:
return Response({
"error": "Не указан тарифный план",
"details": "Необходимо указать plan или account_type"
}, status=status.HTTP_400_BAD_REQUEST)
serializer = PlanChangeSerializer(user_profile, data=request.data)
try:
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
account_type = request.data['account_type']
# получаем тарифный план
new_plan = get_object_or_404(Pricing, plan=account_type)
# создаем транзакцию
transaction = Transactions.objects.create(
user=user,
plan=new_plan,
amount=new_plan.price,
status='success'
)
# если транзакция успешно создана, меняем тариф
if transaction:
serializer.save()
return Response({
"message": "Тариф успешно изменен",
"account_type": new_plan.plan
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
"error": "Ошибка при изменении тарифного плана",
"details": str(e)
}, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class GetMembershipData(ViewSet):
"""Получаем все тарифные планы"""
@action(detail=False, methods=['get'])
@handle_exceptions
def get_pricing_data(self, request):
"""Получаем данные по тарифам"""
pricing_data = Pricing.objects.all().order_by('price')
serializer = PricingSerializer(pricing_data, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class LeadViewSet(ViewSet):
"""ViewSet для работы с заявками на перевозку"""
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['post'])
@handle_exceptions
def send_lead(self, request):
"""
Создание новой заявки на перевозку
"""
# добавляем текущего пользователя в данные
data = request.data.copy()
data['moving_user'] = request.user.id
# проверяем существование и доступность маршрута
try:
route = Route.objects.get(id=data.get('route'))
if route.owner.id == request.user.id:
print("Error: User trying to respond to their own route")
return Response(
{"error": "Вы не можете откликнуться на собственную заявку"},
status=status.HTTP_400_BAD_REQUEST
)
except Route.DoesNotExist:
print("Route not found for ID:", data.get('route'))
return Response(
{"error": "Указанный маршрут не найден"},
status=status.HTTP_404_NOT_FOUND
)
serializer = LeadSerializer(data=data)
is_valid = serializer.is_valid()
if is_valid:
try:
lead: Leads = serializer.save()
# собираем ответ с данными о заявке для фронта
response_data = {
"status": "success",
"message": "Заявка успешно создана",
"data": {
"id": lead.id,
"route_id": lead.route.id,
"moving_price": lead.moving_price,
"moving_date": lead.moving_date,
}
}
# отправляем емаил владельцу маршрута
try:
route_owner_email = route.owner.email
email_subject = f"Новая заявка на перевозку #{lead.id}"
email_content = f"""
<h2>Получена новая заявка на перевозку</h2>
<p>Детали заявки:</p>
<ul>
<li>Номер заявки: {lead.id}</li>
<li>Маршрут: {route.id}</li>
<li>Предложенная цена: {lead.moving_price}</li>
<li>Дата перевозки: {lead.moving_date}</li>
</ul>
"""
email_result = send_email(
to=[route_owner_email],
subject=email_subject,
html_content=email_content
)
if "Ошибка" in email_result:
print(f"Warning: Failed to send email notification: {email_result}")
except Exception as email_error:
print(f"Warning: Error while sending email notification: {str(email_error)}")
return Response(response_data, status=status.HTTP_201_CREATED)
except Exception as e:
return Response(
{
"status": "error",
"message": "Ошибка при сохранении заявки",
"error": str(e)
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
else:
return Response(
{
"status": "error",
"message": "Ошибка валидации данных",
"errors": serializer.errors
},
status=status.HTTP_400_BAD_REQUEST
)
@action(detail=False, methods=['get'])
@handle_exceptions
def get_leads(self, request):
"""Получаем список заявок на перевозку"""
leads = Leads.objects.select_related(
'route',
'route__from_city',
'route__to_city',
'route__from_city__country',
'route__to_city__country',
'route__owner'
).filter(moving_user=request.user).order_by('-created_at')
return Response(
LeadResponseSerializer(leads, many=True).data,
status=status.HTTP_200_OK
)
class PremiumMembershipActionsView(ViewSet):
"""Выделение и поднятие объявления"""
@action(detail=False, methods=['patch'])
@handle_exceptions
def highlight_route(self, request):
"""Выделяем объявление"""
route_id = request.data.get('route_id')
route = get_object_or_404(Route, id=route_id)
if not check_monthly_limit(request.user, route, 'highlight'):
raise PermissionDenied("Превышен лимит выделений за месяц")
# подсвечиваем объявление на 24 часа
now = timezone_now()
route.highlight_end_DT = now + timedelta(days=1)
route.is_highlighted = True
route.save()
# логируем действие
RoutePromotionLog.objects.create(
user=request.user,
route=route,
action_type='highlight'
)
return Response({
"message": "Объявление выделено",
"is_highlighted": route.is_highlighted
}, status=status.HTTP_200_OK)
@action(detail=False, methods=['patch'])
@handle_exceptions
def upper_route(self, request):
"""Поднимаем объявление"""
route_id = request.data.get('route_id')
route = get_object_or_404(Route, id=route_id)
if not check_monthly_limit(request.user, route, 'rising'):
raise PermissionDenied("Превышен лимит поднятий за месяц")
route.rising_DT = timezone_now()
route.save()
# логируем действие
RoutePromotionLog.objects.create(
user=request.user,
route=route,
action_type='rising'
)
return Response({"message": "Объявление поднято"}, status=status.HTTP_200_OK)