import logging import requests import json from django.conf import settings from django.utils import timezone from django.shortcuts import render, reverse, redirect, get_object_or_404 from django.urls import reverse_lazy from django.core.mail import EmailMessage from django.core.exceptions import ObjectDoesNotExist from django.http import JsonResponse, HttpResponseRedirect from django.views.generic.base import RedirectView, TemplateView from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView, FormMixin from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.list import ListView from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib import messages from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from django.forms.models import model_to_dict from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment from accounts.models import User, Address from accounts.utils import get_or_create_customer from accounts.forms import AddressForm as AccountAddressForm, CustomerUpdateForm from core.models import Product, Order, Transaction, OrderLine, Coupon from core.forms import ShippingMethodForm from core import OrderStatus from .forms import AddToCartForm, UpdateCartItemForm, OrderCreateForm, AddressForm, CouponApplyForm, ContactForm from .cart import Cart from .payments import CaptureOrder logger = logging.getLogger(__name__) class CartView(TemplateView): template_name = 'storefront/cart_detail.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) cart = Cart(self.request) for item in cart: for variation in item['variations'].values(): variation['update_quantity_form'] = UpdateCartItemForm( initial={ 'quantity': variation['quantity'] } ) context['cart'] = cart context['coupon_apply_form'] = CouponApplyForm() return context class CartAddProductView(SingleObjectMixin, FormView): model = Product form_class = AddToCartForm def get_success_url(self): return reverse('storefront:cart-detail') def post(self, request, *args, **kwargs): cart = Cart(request) form = self.get_form() if form.is_valid(): cart.add( request=request, product=self.get_object(), grind=form.cleaned_data['grind'], quantity=form.cleaned_data['quantity'] ) return self.form_valid(form) else: return self.form_invalid(form) def form_valid(self, form): return super().form_valid(form) class CartUpdateProductView(SingleObjectMixin, FormView): model = Product form_class = UpdateCartItemForm def get_success_url(self): return reverse('storefront:cart-detail') def post(self, request, *args, **kwargs): cart = Cart(request) form = self.get_form() if form.is_valid(): cart.add( request=request, product=self.get_object(), grind=kwargs['grind'], quantity=form.cleaned_data['quantity'], update_quantity=form.cleaned_data['update'] ) return self.form_valid(form) else: return self.form_invalid(form) def form_valid(self, form): return super().form_valid(form) def cart_remove_product_view(request, pk): cart = Cart(request) product = get_object_or_404(Product, id=pk) cart.remove(product) return redirect('storefront:cart-detail') class CouponApplyView(FormView): template_name = 'contact.html' form_class = CouponApplyForm success_url = reverse_lazy('storefront:cart-detail') def form_valid(self, form): today = timezone.localtime(timezone.now()).date() code = form.cleaned_data['code'] try: coupon = Coupon.objects.get( code__iexact=code, valid_from__date__lte=today, valid_to__date__gte=today ) if coupon.is_valid: self.request.session['coupon_code'] = coupon.code except ObjectDoesNotExist: self.request.session['coupon_code'] = None return super().form_valid(form) class ProductListView(FormMixin, ListView): model = Product template_name = 'storefront/product_list.html' form_class = AddToCartForm ordering = 'sorting' queryset = Product.objects.filter( visible_in_listings=True ) class ProductDetailView(FormMixin, DetailView): model = Product template_name = 'storefront/product_detail.html' form_class = AddToCartForm class CheckoutAddressView(FormView): template_name = 'storefront/checkout_address.html' form_class = AddressForm success_url = reverse_lazy('storefront:order-create') def get_initial(self): user = self.request.user initial = None if user.is_authenticated and user.default_shipping_address: address = user.default_shipping_address initial = { 'first_name': address.first_name, 'last_name': address.last_name, 'email': user.email, 'street_address_1': address.street_address_1, 'street_address_2': address.street_address_2, 'city': address.city, 'state': address.state, 'postal_code': address.postal_code } elif self.request.session.get('shipping_address'): address = self.request.session.get('shipping_address') initial = { 'first_name': address['first_name'], 'last_name': address['last_name'], 'email': address['email'], 'street_address_1': address['street_address_1'], 'street_address_2': address['street_address_2'], 'city': address['city'], 'state': address['state'], 'postal_code': address['postal_code'] } return initial def form_valid(self, form): # save address data to session self.request.session['shipping_address'] = form.cleaned_data return super().form_valid(form) class OrderCreateView(CreateView): model = Order template_name = 'storefront/order_form.html' form_class = OrderCreateForm success_url = reverse_lazy('storefront:payment-done') def get(self, request, *args, **kwargs): if not self.request.session.get("shipping_address"): messages.warning(request, 'Please add a shipping address.') return HttpResponseRedirect(reverse('storefront:checkout-address')) else: return super().get(request, *args, **kwargs) def get_initial(self): cart = Cart(self.request) try: shipping_cost = cart.get_shipping_cost() except Exception as e: raise e('Could not get shipping information') shipping_cost = Decimal('0.00') initial = { 'total_net_amount': cart.get_total_price(), 'shipping_total': shipping_cost } if self.request.session.get('shipping_address'): a = self.request.session.get('shipping_address') user_info = { 'email': a['email'], 'first_name': a['first_name'], 'last_name': a['last_name'] } initial |= user_info return initial def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['shipping_address'] = self.request.session.get('shipping_address') context['PAYPAL_CLIENT_ID'] = settings.PAYPAL_CLIENT_ID return context def form_valid(self, form): cart = Cart(self.request) shipping_address = self.request.session.get('shipping_address') form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address) form.instance.status = OrderStatus.DRAFT self.object = form.save() bulk_list = cart.build_bulk_list(self.object) objs = OrderLine.objects.bulk_create(bulk_list) response = cart.create_order() data = response.result.__dict__['_dict'] self.request.session['order_id'] = self.object.pk return JsonResponse(data) @csrf_exempt @require_POST def paypal_order_transaction_capture(request, transaction_id): if request.method =="POST": data = CaptureOrder().capture_order(transaction_id) cart = Cart(request) cart.clear() order = Order.objects.get(pk=request.session.get('order_id')) order.status = OrderStatus.UNFULFILLED order.save() transaction = Transaction.objects.get(order=order) transaction.paypal_id = data['purchase_units'][0]['payments']['captures'][0]['id'] transaction.status = data['status'] transaction.save() logger.debug(f'\nPayPal Response data: {data}\n') return JsonResponse(data) else: return JsonResponse({'details': 'invalid request'}) @csrf_exempt @require_POST def paypal_webhook_endpoint(request): data = json.loads(request.body) logger.info(data) return JsonResponse(data) class PaymentDoneView(TemplateView): template_name = 'storefront/payment_done.html' class PaymentCanceledView(TemplateView): template_name = 'storefront/payment_canceled.html' class CustomerDetailView(LoginRequiredMixin, DetailView): model = User template_name = 'storefront/customer_detail.html' context_object_name = 'customer' class CustomerUpdateView(LoginRequiredMixin, UpdateView): model = User template_name = 'storefront/customer_form.html' context_object_name = 'customer' form_class = CustomerUpdateForm def get_success_url(self): return reverse('storefront:customer-detail', kwargs={'pk': self.object.pk}) class OrderDetailView(LoginRequiredMixin, DetailView): model = Order template_name = 'storefront/order_detail.html' pk_url_kwarg = 'order_pk' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['customer'] = User.objects.get(pk=self.kwargs['pk']) return context class AddressUpdateView(LoginRequiredMixin, UpdateView): model = Address pk_url_kwarg = 'address_pk' template_name = 'storefront/address_form.html' form_class = AccountAddressForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['customer'] = User.objects.get(pk=self.kwargs['pk']) return context def get_success_url(self): return reverse('storefront:customer-detail',kwargs={'pk': self.kwargs['pk']}) class AboutView(TemplateView): template_name = 'storefront/about.html' class FairTradeView(TemplateView): template_name = 'storefront/fairtrade.html' class ReviewListView(TemplateView): template_name = 'storefront/reviews.html' class ContactFormView(FormView, SuccessMessageMixin): template_name = 'storefront/contact_form.html' form_class = ContactForm success_url = reverse_lazy('storefront:product-list') def form_valid(self, form): form.send_email() return super().form_valid(form)