Files
smartsoltech_site/smartsoltech/web/views.py
2025-11-24 11:31:29 +09:00

457 lines
21 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 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})