/
HitriyKrab
/
DjangoProjectShop
Обзор
Документация
Войти
/
HitriyKrab
/
DjangoProjectShop
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
MyShop/views.py
445 строк
22 KB
ХитрыйКраб
Добавлено: самые обсуждаемые товары
16 ноя 2025, 15:37
16 ноя 2025, 15:37
e083dd9
Код
Авторство
О чём код?
from django.http import HttpRequest, HttpResponse, Http404 from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render, reverse from django.contrib.auth.models import User from django.views import View from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView from django.urls import reverse_lazy from django.shortcuts import get_object_or_404, redirect from django.db.models import Count from timeit import default_timer import random from .models import Category, Product, Order, Cart, Review, Payment, DeliverySettings # Представление для отображения стартовой страницы приложения class ShopIndexView(View): def get(self, request: HttpRequest) -> HttpResponse: # Товары, сортируем по индексу сортировки, затем по количеству покупок popular_products = Product.objects.all().order_by('-sort_index', '-purchase_count')[:3] limited_edition_products = Product.objects.filter(limited_edition=True)[:3] reviewed_products = Product.objects.annotate(review_count=Count('reviews')).order_by('-review_count')[:3] # Контекст для передачи данных в шаблон в виде словаря context = { # Время работы приложения (с первого запуска, в секундах) 'time_running': round(default_timer()), # Выбор категории товара (для выбора товаров определенной категории) 'categories': Category.objects.all(), # Число товаров магазина (для перехода к списку товаров) 'all_products': Product.objects.count, 'popular_products': popular_products, 'limited_edition_products': limited_edition_products, 'reviewed_products': reviewed_products, } return render(request, 'MyShop/home.html', context=context) # Категории товаров class CategoryListView(ListView): # Модель "Категория" model = Category # Переменная для шаблона context_object_name = 'categories' # Шаблон template_name = 'MyShop/category_list.html' # Товары выбранной категории (детали категории) class CategoryDetailView(DetailView): # Модель model = Category # Переменная для шаблона context_object_name = 'category' # Шаблон template_name = 'MyShop/category_detail.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['products'] = self.object.products.all() return context # Представление с информацией о товаре class ProductDetailView(DetailView): # Имя шаблона, который будет отрисоываться при выполнении представления template_name = 'MyShop/product_detail.html' # Запрос к выбранному товару с подгрузкой категорий из БД queryset = Product.objects.select_related('category') # Переменная в шаблоне с содержанием данного представления context_object_name = 'product' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Добавляем в контекст все отзывы для товара context['reviews'] = self.object.reviews.all() return context # Представление со списком товаров class ProductsListView(ListView): # Шаблон template_name = 'MyShop/products_list.html' # Запрос к товарам с подгрузкой категорий из БД queryset = Product.objects.select_related('category') # Для шаблона context_object_name = 'products' # Представление с инфо о заказе class OrderDetailView(DetailView): # Запрос к БД с подгрузкой пользователя и товаров для выбранного заказа queryset = Order.objects.select_related('user').prefetch_related('products') # Шаблон template_name = 'MyShop/order_detail.html' # Переменная для шаблона context_object_name = 'order' # Представление со списком заказов аутентифицированного пользователя class OrdersListView(LoginRequiredMixin, ListView): # Набор запросов к заказам в БД (подгрузка пользователя-заказчика и товаров в заказе) queryset = Order.objects.select_related('user').prefetch_related('products') # Шаблон template_name = 'MyShop/orders_list.html' # Переменная для шаблона context_object_name = 'orders' # Получение списка заказов пользователя def get_queryset(self): try: # Получение пользователя по id self.owner = User.objects.get(pk=self.kwargs['pk']) except User.DoesNotExist: raise Http404("Пользователь не найден") # Получение заказов пользователя return Order.objects.filter(user=self.owner) # Передача контекста (owner) в шаблон def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['owner'] = self.owner return context # Представление для создания заказа (LoginRequiredMixin - только для аутентифицированных) class OrderCreateView(LoginRequiredMixin, CreateView): # Модель "Заказ", на основе которой создаётся заказ model = Order # Поля в модели, которые берутся из модели и заполняются при создании заказа fields = ['delivery_address', 'delivery_method', 'products', 'city'] # Шаблон template_name = 'MyShop/order_create.html' # Адрес созданного заказа, на который попадёт пользователь при успешном создании def get_success_url(self): return reverse('MyShop:order', kwargs={'pk': self.object.pk}) def form_valid(self, form): # Вычисляем пользователя form.instance.user = self.request.user # Сохраняем объект без commit, чтобы можно было работать с ним self.object = form.save(commit=False) # Получаем товары из формы products = form.cleaned_data.get('products') total_price = sum(product.price for product in products) if products else 0 # Получаем настройки доставки delivery_settings = DeliverySettings.objects.first() if not delivery_settings: delivery_settings = DeliverySettings.objects.create() # Рассчитываем стоимость доставки if self.object.delivery_method == Order.SEC_DELIVERY: delivery_cost = delivery_settings.express_delivery_extra_price elif self.object.delivery_method == Order.FST_DELIVERY: if total_price < delivery_settings.free_delivery_threshold: delivery_cost = delivery_settings.standard_delivery_price else: delivery_cost = 0 else: delivery_cost = 0 # Прибавляем стоимость доставки к общей цене self.object.total_price = total_price + delivery_cost # Сохраняем заказ self.object.save() form.save_m2m() return super().form_valid(form) # Представление для изменения заказа class OrderUpdateView(UpdateView): # Модель "Заказ", в соответствии с которой заказ будет обновлён model = Order # Поля в модели, которые берутся из модели и заполняются при изменении заказа fields = ['delivery_address', 'city', 'products', 'delivery_method'] # Шаблон template_name = 'MyShop/order_update.html' def form_valid(self, form): # Сохраняем объект без commit, чтобы можно было работать с ним self.object = form.save(commit=False) # Получаем товары, выбранные в форме products = form.cleaned_data.get('products') total_price = sum(product.price for product in products) if products else 0 # Получаем настройки доставки delivery_settings = DeliverySettings.objects.first() if not delivery_settings: delivery_settings = DeliverySettings.objects.create() # Рассчитываем стоимость доставки if self.object.delivery_method == Order.SEC_DELIVERY: delivery_cost = delivery_settings.express_delivery_extra_price elif self.object.delivery_method == Order.FST_DELIVERY: if total_price < delivery_settings.free_delivery_threshold: delivery_cost = delivery_settings.standard_delivery_price else: delivery_cost = 0 else: delivery_cost = 0 # Прибавляем стоимость доставки к общей цене self.object.total_price = total_price + delivery_cost # Сохраняем объект с обновлённым total_price self.object.save() form.save_m2m() return super().form_valid(form) # Адрес обновлённого заказа, куда будет перенаправлен пользователь при успешном обновлении def get_success_url(self): return reverse('MyShop:order', kwargs={'pk': self.object.pk}) # Для удаления заказа class OrderDeleteView(DeleteView): # Модель "Заказ", из БД которой будет удалён объект model = Order # Шаблон template_name = 'MyShop/order_delete.html' # Переменная в шаблоне context_object_name = 'order' # При удалении заказа пользователь будет перенаправлен в свой профиль (там есть заказы) success_url = reverse_lazy('MyAuth:me') # Корзина class CartDetailView(DetailView): # Указываем, с какой моделью будем работать — в данном случае корзина (Cart) queryset = Cart.objects.select_related('user').prefetch_related('products') # Шаблон, который будет рендериться при обращении к этому view template_name = 'MySHop/cart_detail.html' # Имя переменной в шаблоне для объекта корзины (вместо стандартного 'object') context_object_name = 'cart' # Переопределяем способ получения объекта корзины. def get_object(self, queryset=None): # Получаем корзину текущего пользователя через user поле (сли корзина не существует, создаём новую) cart, created = Cart.objects.get_or_create(user=self.request.user) # Возвращаем объект корзины, который будет доступен в шаблоне под именем 'cart' return cart # Добавление товара в корзину class AddToCartView(LoginRequiredMixin, View): # Метод POST будет обрабатывать запросы на добавление товара в корзину def post(self, request, pk): # Получаем объект товара по primary key из URL. Если товара нет — возвращаем 404 product = get_object_or_404(Product, pk=pk) # Получаем корзину текущего пользователя или создаём новую, если ещё нет cart, created = Cart.objects.get_or_create(user=request.user) # Добавляем товар в ManyToMany поле products корзины cart.products.add(product) # После добавления товара перенаправляем пользователя обратно на страницу, # с которой пришёл (HTTP_REFERER) или на главную (/) если заголовок не передан return redirect(request.META.get('HTTP_REFERER', '/')) class CartOrderCreateView(LoginRequiredMixin, CreateView): # Модель, на основе которой будет создан заказ из товаров в корзине model = Order # Поля, которые берутся из модели и заполняются при создании заказа fields = ['delivery_address', 'city', 'delivery_method'] # Шаблон template_name = 'MyShop/cart_order_create.html' # Добавляем в контекст шаблона список товаров из корзины текущего пользователя def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) cart = Cart.objects.filter(user=self.request.user).first() context['cart_products'] = cart.products.all() if cart else [] return context # При успешной валидации формы текущий пользователь = заказчик (заказ создаётся, # но пока не сохраняется в базу (commit=False)) def form_valid(self, form): form.instance.user = self.request.user self.object = form.save(commit=False) # Получаем корзину со стоимостью всех товаров в ней cart = Cart.objects.filter(user=self.request.user).first() if cart and cart.products.exists(): total_price = sum(product.price for product in cart.products.all()) else: total_price = 0 # Получаем настройки доставки delivery_settings = DeliverySettings.objects.first() if not delivery_settings: delivery_settings = DeliverySettings.objects.create() # Рассчитываем стоимость доставки if self.object.delivery_method == Order.SEC_DELIVERY: delivery_cost = delivery_settings.express_delivery_extra_price elif self.object.delivery_method == Order.FST_DELIVERY: if total_price < delivery_settings.free_delivery_threshold: delivery_cost = delivery_settings.standard_delivery_price else: delivery_cost = 0 else: delivery_cost = 0 # Прибавляем стоимость доставки к общей цене self.object.total_price = total_price + delivery_cost self.object.save() # Привязываем товары из корзины к заказу и очищаем корзину if cart and cart.products.exists(): self.object.products.set(cart.products.all()) cart.products.clear() return super().form_valid(form) def get_success_url(self): return reverse('MyShop:order', kwargs={'pk': self.object.pk}) # Удаление товара из корзины class CartProductDeleteView(LoginRequiredMixin, View): def post(self, request, *args, **kwargs): # Получаем pk товара из параметров URL (self.kwargs) product_pk = self.kwargs.get('pk') # Получаем корзину текущего пользователя (предполагается, что одна корзина на пользователя) cart = Cart.objects.get(user=request.user) # Получаем товар из корзины по pk, если товара с таким pk в корзине нет — вернётся 404 ошибка product = get_object_or_404(cart.products, pk=product_pk) # Удаляем товар из корзины (связь товара с корзиной), но сам товар в базе данных останется cart.products.remove(product) # После удаления перенаправляем пользователя обратно на страницу корзины return redirect('MyShop:cart') # Создание комментария зарегистрированным пользователем class ReviewCreateView(LoginRequiredMixin, CreateView): # Модель, на основе которой пользователь создаёт комментарий model = Review # Поле для комментария fields = ['comment'] # Шаблон template_name = 'MyShop/review_create.html' def form_valid(self, form): # Привязываем отзыв к товару и к текущему пользователю product = get_object_or_404(Product, pk=self.kwargs['pk']) form.instance.product = product form.instance.user = self.request.user return super().form_valid(form) def get_success_url(self): # После успешного сохранения возвращаемся на страницу товара return reverse_lazy('MyShop:product', kwargs={'pk': self.kwargs['pk']}) # Очередь для хранения платежей, ожидающих обработки payment_queue = [] # "Платежный сервис" def fake_payment_service(number_str): number = int(number_str) # если четный и не заканчивается на 0 -> подтвердить if number % 2 == 0 and not number_str.endswith('0'): return True, None else: errors = ["Ошибка подключения", "Недостаточно средств", "Ошибка сети"] return False, random.choice(errors) # Обработка оплаты заказа class OrderPayView(View): template_name = 'MyShop/order_pay.html' def get(self, request, pk): order = get_object_or_404(Order, pk=pk, user=request.user) return render(request, self.template_name, {'order': order}) def post(self, request, pk): # Обработка отправленной формы оплаты order = get_object_or_404(Order, pk=pk, user=request.user) method = request.POST.get('method') number = None if method == 'card': number = request.POST.get('card_number') elif method == 'random_account': number = request.POST.get('account_number') # Валидация (номер карты чётный и не длиннее 8 цифр) if not number or len(number) > 8 or not number.isdigit() or int(number) % 2 != 0: return render(request, self.template_name, { 'order': order, 'error': 'Номер должен быть чётным и не длиннее восьми цифр', }) # Валидно: статус платежа и заказа меняется на «Оплачен», и пользователь переходит на страницу заказа payment = Payment.objects.create( order=order, user=request.user, amount=order.total_price, method=method, status='В обработке', ) # Имитируем вызов сервиса оплаты (синхронно для примера) number_str = number number_int = int(number_str) if number_int % 2 == 0 and not number_str.endswith('0'): # Успешная оплата payment.status = 'Оплачен' payment.save() order.status = 'Оплачен' order.save() # Редирект на страницу заказа при успехе return redirect(reverse('MyShop:order', kwargs={'pk': order.pk})) else: # Ошибка оплаты payment.status = 'Ошибка' payment.save() error_message = 'Возникла ошибка при оплате. Пожалуйста, попробуйте снова.' return render(request, self.template_name, { 'order': order, 'error': error_message, }) # Обработка очереди платежей (запускается отдельно, например через cron или Celery) def process_payment_queue(): while payment_queue: payment_pk, number = payment_queue.pop(0) payment = Payment.objects.get(pk=payment_pk) success, error = fake_payment_service(number) if success: payment.status = 'Оплачен' payment.save() order = payment.order order.status = 'Оплачен' order.save() else: payment.status = 'Ошибка' payment.save()