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, PermissionDenied 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, grind): cart = Cart(request) product = get_object_or_404(Product, id=pk) cart.remove(product, grind) 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'].upper() 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 = { 'full_name': address.first_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 = { 'full_name': address['first_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 cleaned_data = form.cleaned_data first_name, last_name = form.process_full_name( cleaned_data.get('full_name') ) address = { 'first_name': first_name, 'last_name': last_name, 'email': cleaned_data['email'], 'street_address_1': cleaned_data['street_address_1'], 'street_address_2': cleaned_data['street_address_2'], 'city': cleaned_data['city'], 'state': cleaned_data['state'], 'postal_code': cleaned_data['postal_code'] } self.request.session['shipping_address'] = address 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') ) elif self.request.session.get('coupon_code'): address = self.request.session.get("shipping_address") coupon = Coupon.objects.get( code=self.request.session.get('coupon_code') ) try: user = User.objects.get(email=address['email']) except ObjectDoesNotExist: user = None if user in coupon.users.all(): del self.request.session['coupon_code'] messages.warning(request, 'Coupon already used.') 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) order = Order.objects.get(pk=request.session.get('order_id')) order.status = OrderStatus.UNFULFILLED try: coupon = Coupon.objects.get( code=request.session.get('coupon_code') ) except ObjectDoesNotExist: coupon = None if coupon: order.coupon = coupon coupon.users.add(order.customer) 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() cart.clear() 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(UserPassesTestMixin, LoginRequiredMixin, DetailView): model = User template_name = 'storefront/customer_detail.html' context_object_name = 'customer' permission_denied_message = 'Not authorized.' raise_exception = True def test_func(self): return self.request.user.pk == self.get_object().pk class CustomerUpdateView(UserPassesTestMixin, LoginRequiredMixin, UpdateView): model = User template_name = 'storefront/customer_form.html' context_object_name = 'customer' form_class = CustomerUpdateForm permission_denied_message = 'Not authorized.' raise_exception = True def test_func(self): return self.request.user.pk == self.get_object().pk def get_success_url(self): return reverse( 'storefront:customer-detail', kwargs={'pk': self.object.pk} ) class OrderDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView): model = Order template_name = 'storefront/order_detail.html' pk_url_kwarg = 'order_pk' permission_denied_message = 'Not authorized.' raise_exception = True def test_func(self): return self.request.user.pk == self.get_object().customer.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 CustomerAddressCreateView( UserPassesTestMixin, LoginRequiredMixin, CreateView ): model = Address template_name = 'storefront/address_create_form.html' form_class = AccountAddressForm permission_denied_message = 'Not authorized.' raise_exception = True def test_func(self): return self.request.user.pk == self.kwargs['pk'] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['customer'] = User.objects.get(pk=self.kwargs['pk']) return context def form_valid(self, form): customer = User.objects.get(pk=self.kwargs['pk']) self.object = form.save() customer.addresses.add(self.object) return super().form_valid(form) def get_success_url(self): return reverse( 'storefront:customer-detail', kwargs={'pk': self.kwargs['pk']} ) class CustomerAddressUpdateView( UserPassesTestMixin, LoginRequiredMixin, UpdateView ): model = Address pk_url_kwarg = 'address_pk' template_name = 'storefront/address_form.html' form_class = AccountAddressForm permission_denied_message = 'Not authorized.' raise_exception = True def test_func(self): return self.request.user.pk == self.get_object().customer.pk 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)