from django.shortcuts import render, get_object_or_404, redirect from .models import ( Service, Project, Client, BlogPost, Review, Order, ServiceRequest, Category, AboutPage, FooterSettings, TeamMember, NewsArticle, CareerVacancy, PortfolioItem, PrivacyPolicy, TermsOfUse ) from django.db.models import Avg from comunication.models import TelegramSettings import qrcode import os import io import base64 from django.conf import settings import uuid from django.utils.http import urlsafe_base64_encode from django.utils.encoding import force_bytes from django.http import JsonResponse from django.utils.crypto import get_random_string from django.contrib.auth.models import User from decouple import config from django.urls import reverse from django.contrib.auth.decorators import login_required from django.core.mail import send_mail from comunication.telegram_bot import TelegramBot import hmac import hashlib import json from django.views.decorators.csrf import csrf_exempt import logging from django.db import transaction, IntegrityError from django.db.models import Q logger = logging.getLogger(__name__) # Initialize Telegram Bot try: bot = TelegramBot() except Exception as e: logger.error(f"Failed to initialize Telegram bot: {str(e)}") def home(request): services = Service.objects.all()[:6] # Показываем только первые 6 услуг на главной # Последние посты блога recent_blog_posts = BlogPost.objects.filter(status=BlogPost.PUBLISHED).order_by('-published_date')[:2] # Последние новости recent_news = NewsArticle.objects.filter(is_published=True).order_by('-published_date')[:2] # Избранные проекты портфолио featured_portfolio = PortfolioItem.objects.filter(featured=True, is_active=True)[:3] # Количество активных вакансий active_vacancies_count = CareerVacancy.objects.filter(is_active=True).count() context = { 'services': services, 'recent_blog_posts': recent_blog_posts, 'recent_news': recent_news, 'featured_portfolio': featured_portfolio, 'active_vacancies_count': active_vacancies_count, } return render(request, 'web/home_modern.html', context) def service_detail(request, pk): service = get_object_or_404(Service, pk=pk) projects_in_category = Project.objects.filter(category=service.category) average_rating = service.reviews.aggregate(Avg('rating'))['rating__avg'] or 0 total_reviews = service.reviews.count() reviews = service.reviews.all() return render(request, 'web/service_detail_modern.html', { 'service': service, 'projects_in_category': projects_in_category, 'average_rating': average_rating, 'total_reviews': total_reviews, 'reviews': reviews, }) def project_detail(request, pk): project = get_object_or_404(Project, pk=pk) return render(request, 'web/project_detail.html', {'project': project}) def client_detail(request, pk): client = get_object_or_404(Client, pk=pk) return render(request, 'web/client_detail.html', {'client': client}) def blog_post_detail(request, pk): blog_post = get_object_or_404(BlogPost, pk=pk) return render(request, 'web/blog_post_detail.html', {'blog_post': blog_post}) def services_view(request): # Получаем выбранную категорию из GET параметра category_id = request.GET.get('category') # Получаем все категории для фильтров categories = Category.objects.all() # Фильтруем услуги по категории, если выбрана if category_id: services = Service.objects.filter(category_id=category_id) selected_category = Category.objects.filter(id=category_id).first() else: services = Service.objects.all() selected_category = None context = { 'services': services, 'categories': categories, 'selected_category': selected_category, } return render(request, 'web/services_modern.html', context) def about_view(request): about_page = AboutPage.objects.filter(is_active=True).first() team_members = TeamMember.objects.filter(is_active=True).order_by('order', 'last_name') return render(request, 'web/about_modern.html', { 'about': about_page, 'team_members': team_members }) def create_service_request(request, service_id): if request.method == 'POST': try: data = json.loads(request.body) client_email = data.get('client_email') client_phone = data.get('client_phone') client_name = data.get('client_name') if not all([client_email, client_phone, client_name]): return JsonResponse({'status': 'error', 'message': 'Все поля должны быть заполнены'}, status=400) service = get_object_or_404(Service, pk=service_id) # Проверяем наличие клиента по email (так как email должен быть уникальным) client, client_created = Client.objects.get_or_create( email=client_email, defaults={ 'first_name': client_name.split()[0] if client_name else "", 'last_name': client_name.split()[-1] if len(client_name.split()) > 1 else "", 'phone_number': client_phone, } ) # Обновляем данные клиента, если он уже существовал (например, телефон или имя изменились) if not client_created: client.first_name = client_name.split()[0] client.last_name = client_name.split()[-1] if len(client_name.split()) > 1 else "" client.phone_number = client_phone client.save() # Проверяем наличие заявки на эту же услугу, не завершенной и не подтвержденной existing_requests = ServiceRequest.objects.filter(client=client, service=service, is_verified=False) if existing_requests.exists(): return JsonResponse({ 'status': 'existing_request', 'message': 'У вас уже есть активная заявка на данную услугу. Пожалуйста, проверьте ваш Telegram для завершения процесса.' }) # Создаем новую заявку для клиента token = uuid.uuid4().hex service_request = ServiceRequest.objects.create( service=service, client=client, token=token, is_verified=False ) return JsonResponse({ 'status': 'success', 'message': 'Заявка успешно создана. Пожалуйста, проверьте ваш Telegram для подтверждения.', 'service_request_id': service_request.id, }) except json.JSONDecodeError: logger.error("Invalid JSON format") return JsonResponse({'status': 'error', 'message': 'Неверный формат данных'}, status=400) return JsonResponse({'status': 'error', 'message': 'Метод запроса должен быть POST'}, status=405) def generate_qr_code(request, service_id): if request.method == 'POST': try: data = json.loads(request.body) client_email = data.get('client_email') client_phone = data.get('client_phone') client_name = data.get('client_name') if not all([client_email, client_phone, client_name]): logger.error("Все поля должны быть заполнены") return JsonResponse({'error': 'Все поля должны быть заполнены'}, status=400) # Используем транзакцию для предотвращения конкурентного создания дубликатов with transaction.atomic(): user, user_created = User.objects.select_for_update().get_or_create( email=client_email, defaults={ "username": f"{client_email.split('@')[0]}_{get_random_string(5)}", "first_name": client_name.split()[0] if client_name else "", "last_name": client_name.split()[-1] if len(client_name.split()) > 1 else "" } ) if not user_created: # Обновляем информацию о пользователе, если он уже существует user.first_name = client_name.split()[0] if client_name else "" user.last_name = client_name.split()[-1] if len(client_name.split()) > 1 else "" user.save() client, client_created = Client.objects.select_for_update().get_or_create( email=client_email, defaults={ 'user': user, 'first_name': user.first_name, 'last_name': user.last_name, 'phone_number': client_phone } ) if not client_created: # Обновляем информацию о клиенте, если он уже существует client.first_name = user.first_name client.last_name = user.last_name client.phone_number = client_phone client.save() # Проверка на существование активной заявки service = get_object_or_404(Service, pk=service_id) existing_requests = ServiceRequest.objects.filter(client=client, service=service, is_verified=False) if existing_requests.exists(): return JsonResponse({ 'status': 'existing_request', 'message': 'У вас уже есть активная заявка на данную услугу. Пожалуйста, проверьте ваш Telegram для завершения процесса.' }) # Создаем новую заявку на услугу token = uuid.uuid4().hex service_request = ServiceRequest.objects.create( service=service, client=client, token=token, is_verified=False ) logger.info(f"Создана новая заявка: {service_request.id} для клиента: {client.email}") # Получаем настройки Telegram бота из БД telegram_settings = get_object_or_404(TelegramSettings, pk=1) bot_username = telegram_settings.bot_name # Генерация ссылки для Telegram registration_link = f'https://t.me/{bot_username}?start=request_{service_request.id}_token_{urlsafe_base64_encode(force_bytes(token))}' # Генерируем QR-код в памяти (без сохранения на диск) qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(registration_link) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") # Конвертируем изображение в base64 buffer = io.BytesIO() img.save(buffer, format='PNG') qr_code_base64 = base64.b64encode(buffer.getvalue()).decode() qr_code_data_url = f'data:image/png;base64,{qr_code_base64}' logger.info(f"QR-код сгенерирован в памяти для заявки {service_request.id}") except IntegrityError as e: logger.error(f"Ошибка целостности данных при создании пользователя или клиента: {str(e)}") return JsonResponse({'error': 'Ошибка при обработке данных. Пожалуйста, попробуйте позже.'}, status=500) except Exception as e: logger.error(f"Ошибка при обработке запроса: {str(e)}") return JsonResponse({'error': f'Ошибка: {str(e)}'}, status=500) return JsonResponse({ 'registration_link': registration_link, 'qr_code_url': qr_code_data_url, 'service_request_id': service_request.id, 'client_email': client_email, 'client_phone': client_phone, 'client_name': client_name }) return JsonResponse({'error': 'Метод запроса должен быть POST'}, status=405) def request_status(request, service_id): try: service_request = get_object_or_404(ServiceRequest, pk=service_id) client = service_request.client is_verified = service_request.is_verified return JsonResponse({ 'is_verified': is_verified, 'client_name': client.first_name if client else "Неизвестно", 'client_chat_id': client.chat_id if client else None, }) except Exception as e: logger.error(f"Ошибка при получении статуса заявки: {str(e)}") return JsonResponse({'error': str(e)}, status=500) @csrf_exempt def send_telegram_notification(request): if request.method == 'POST': try: data = json.loads(request.body) # Логируем полученные данные для отладки logging.info(f"Полученные данные для подтверждения заявки: {data}") service_request_id = data.get('service_request_id') client_chat_id = data.get('client_chat_id') client_name = data.get('client_name') if not service_request_id or not client_chat_id: return JsonResponse({'error': 'Недостаточно данных для подтверждения'}, status=400) # Проверяем существование заявки service_request = ServiceRequest.objects.filter(id=service_request_id).first() if not service_request: return JsonResponse({'error': 'Заявка не найдена'}, status=404) # Обновляем заявку с chat_id, если все верно service_request.chat_id = client_chat_id service_request.is_verified = True service_request.save() return JsonResponse({'status': 'Уведомление успешно отправлено в Telegram'}) except json.JSONDecodeError as e: logging.error(f"Ошибка при декодировании JSON: {e}") return JsonResponse({'error': 'Неверный формат данных'}, status=400) logging.error(f"Неподдерживаемый метод запроса: {request.method}") return JsonResponse({'error': 'Метод запроса должен быть POST'}, status=405) def generate_secure_token(service_request_id, secret_key): """Генерация безопасного токена для подтверждения подлинности запроса.""" data = f'{service_request_id}:{secret_key}' return hmac.new(secret_key.encode(), data.encode(), hashlib.sha256).hexdigest() def complete_registration(request, request_id): service_request = get_object_or_404(ServiceRequest, pk=request_id) if request.method == 'POST': client_email = request.POST.get('client_email', service_request.client.email) client_phone = request.POST.get('client_phone', service_request.client.phone_number) chat_id = request.POST.get('chat_id', service_request.chat_id) if not all([client_email, client_phone, chat_id]): return JsonResponse({'status': 'error', 'message': 'Все поля должны быть заполнены.'}, status=400) client = service_request.client client.email = client_email client.phone_number = client_phone client.save() service_request.chat_id = chat_id service_request.save() return JsonResponse({'status': 'success', 'message': 'Регистрация успешно завершена.'}) return render(request, 'web/complete_registration.html', {'service_request': service_request}) def check_request_status(request, request_id): """API endpoint для проверки статуса подтверждения заявки""" try: service_request = get_object_or_404(ServiceRequest, pk=request_id) return JsonResponse({ 'is_verified': service_request.is_verified, 'chat_id': service_request.chat_id, 'created_at': service_request.created_at.isoformat() if service_request.created_at else None }) except ServiceRequest.DoesNotExist: return JsonResponse({'error': 'Заявка не найдена'}, status=404) except Exception as e: logger.error(f"Ошибка при проверке статуса заявки {request_id}: {str(e)}") return JsonResponse({'error': 'Ошибка сервера'}, status=500) # ========== Blog Views ========== def blog_list(request): """Список всех опубликованных постов блога""" posts = BlogPost.objects.filter(status=BlogPost.PUBLISHED).order_by('-published_date') return render(request, 'web/blog_list.html', {'posts': posts}) def blog_detail(request, slug): """Детальная страница поста блога""" post = get_object_or_404(BlogPost, slug=slug, status=BlogPost.PUBLISHED) post.views += 1 post.save(update_fields=['views']) return render(request, 'web/blog_detail.html', {'post': post}) # ========== News Views ========== def news_list(request): """Список всех опубликованных новостей""" news = NewsArticle.objects.filter(is_published=True).order_by('-published_date') return render(request, 'web/news_list.html', {'news': news}) def news_detail(request, slug): """Детальная страница новости""" article = get_object_or_404(NewsArticle, slug=slug, is_published=True) return render(request, 'web/news_detail.html', {'article': article}) # ========== Portfolio Views ========== def portfolio_list(request): """Список всех активных элементов портфолио""" category_id = request.GET.get('category') items = PortfolioItem.objects.filter(is_active=True) if category_id: items = items.filter(category_id=category_id) categories = Category.objects.all() return render(request, 'web/portfolio_list.html', { 'items': items, 'categories': categories }) def portfolio_detail(request, slug): """Детальная страница элемента портфолио""" item = get_object_or_404(PortfolioItem, slug=slug, is_active=True) return render(request, 'web/portfolio_detail.html', {'item': item}) # ========== Career Views ========== def career_list(request): """Список всех активных вакансий""" vacancies = CareerVacancy.objects.filter(is_active=True).order_by('-posted_at') return render(request, 'web/career_list.html', {'vacancies': vacancies}) def career_detail(request, slug): """Детальная страница вакансии""" vacancy = get_object_or_404(CareerVacancy, slug=slug, is_active=True) return render(request, 'web/career_detail.html', {'vacancy': vacancy}) # ========== Legal Pages Views ========== def privacy_policy(request): """Страница политики конфиденциальности""" policy = PrivacyPolicy.objects.filter(is_active=True).first() return render(request, 'web/privacy_policy.html', {'policy': policy}) def terms_of_use(request): """Страница условий использования""" terms = TermsOfUse.objects.filter(is_active=True).first() return render(request, 'web/terms_of_use.html', {'terms': terms})