From 157296db2b70905537a8b7cadf6c00aee627b004 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 29 Oct 2022 10:20:52 -0600 Subject: [PATCH] Add basic shipping checkout defaults --- src/accounts/models.py | 8 ++--- ...002_shippingrate_is_selectable_and_more.py | 24 ++++++++++++++ src/core/models.py | 30 +++++++++++------- .../templates/dashboard/customer_list.html | 19 +++++++++++ src/dashboard/templates/dashboard/stock.html | 25 +++++++++++++++ src/dashboard/urls.py | 5 +++ src/dashboard/views.py | 15 +++++++++ src/static/images/warehouse.png | Bin 0 -> 5123 bytes src/storefront/cart.py | 18 +++++++++-- src/storefront/views.py | 28 ++++++++++++---- src/templates/dashboard.html | 6 ++++ 11 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 src/core/migrations/0002_shippingrate_is_selectable_and_more.py create mode 100644 src/dashboard/templates/dashboard/stock.html create mode 100644 src/static/images/warehouse.png diff --git a/src/accounts/models.py b/src/accounts/models.py index 1647c59..bdda95d 100644 --- a/src/accounts/models.py +++ b/src/accounts/models.py @@ -19,10 +19,10 @@ class Address(models.Model): def __str__(self): return f""" - {first_name} {last_name} - {street_address_1} - {street_address_2} - {city}, {state}, {postal_code} + {self.first_name} {self.last_name} + {self.street_address_1} + {self.street_address_2} + {self.city}, {self.state}, {self.postal_code} """ diff --git a/src/core/migrations/0002_shippingrate_is_selectable_and_more.py b/src/core/migrations/0002_shippingrate_is_selectable_and_more.py new file mode 100644 index 0000000..130ee1b --- /dev/null +++ b/src/core/migrations/0002_shippingrate_is_selectable_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0.2 on 2022-10-29 14:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='shippingrate', + name='is_selectable', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='sitesettings', + name='default_shipping_rate', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='core.shippingrate'), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 2962905..3e290e7 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -56,17 +56,6 @@ class SingletonBase(models.Model): abstract = True -class SiteSettings(SingletonBase): - usps_user_id = models.CharField(max_length=255) - - def __str__(self): - return 'Site Settings' - - class Meta: - verbose_name = 'Site Settings' - verbose_name_plural = 'Site Settings' - - class ProductCategory(models.Model): name = models.CharField(max_length=255) main_category = models.BooleanField(default=True) @@ -271,6 +260,7 @@ class ShippingRate(models.Model): blank=True, null=True ) + is_selectable = models.BooleanField(default=True) def get_absolute_url(self): return reverse('dashboard:rate-detail', kwargs={'pk': self.pk}) @@ -490,3 +480,21 @@ class Subscription(models.Model): on_delete=models.SET_NULL, null=True ) + + +class SiteSettings(SingletonBase): + usps_user_id = models.CharField(max_length=255) + default_shipping_rate = models.ForeignKey( + ShippingRate, + blank=True, + null=True, + related_name='+', + on_delete=models.SET_NULL + ) + + def __str__(self): + return 'Site Settings' + + class Meta: + verbose_name = 'Site Settings' + verbose_name_plural = 'Site Settings' diff --git a/src/dashboard/templates/dashboard/customer_list.html b/src/dashboard/templates/dashboard/customer_list.html index 5387de4..d7aac40 100644 --- a/src/dashboard/templates/dashboard/customer_list.html +++ b/src/dashboard/templates/dashboard/customer_list.html @@ -22,5 +22,24 @@ No customers {% endfor %} +
+ +
{% endblock content %} diff --git a/src/dashboard/templates/dashboard/stock.html b/src/dashboard/templates/dashboard/stock.html new file mode 100644 index 0000000..fe545ed --- /dev/null +++ b/src/dashboard/templates/dashboard/stock.html @@ -0,0 +1,25 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

Stock

+
+ +
+
+

Products

+
+
+ {% for variant in variant_list %} +
+

{{ variant }}

+

Variant ID: {{ variant.pk }}

+

Total in warehouse: {{ variant.stock }}

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/src/dashboard/urls.py b/src/dashboard/urls.py index b054de5..c2b6fdf 100644 --- a/src/dashboard/urls.py +++ b/src/dashboard/urls.py @@ -17,6 +17,11 @@ urlpatterns = [ views.CatalogView.as_view(), name='catalog' ), + path( + 'stock/', + views.StockView.as_view(), + name='stock' + ), path( 'shipping-rates/new/', diff --git a/src/dashboard/views.py b/src/dashboard/views.py index de64a44..7e5814e 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -101,6 +101,20 @@ class CatalogView(ListView): return context +class StockView(ListView): + model = ProductVariant + context_object_name = 'variant_list' + template_name = 'dashboard/stock.html' + + def get_queryset(self): + object_list = ProductVariant.objects.filter( + track_inventory=True + ).order_by('product') + # quantity + # quantity_fulfilled + return object_list + + class ShippingRateDetailView(LoginRequiredMixin, DetailView): model = ShippingRate context_object_name = 'rate' @@ -487,6 +501,7 @@ class ProductOptionDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteVie class CustomerListView(LoginRequiredMixin, ListView): model = User template_name = 'dashboard/customer_list.html' + paginate_by = 100 def get_queryset(self): object_list = User.objects.filter( diff --git a/src/static/images/warehouse.png b/src/static/images/warehouse.png new file mode 100644 index 0000000000000000000000000000000000000000..bbede2846489fdd774149262167c94c8119b5820 GIT binary patch literal 5123 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 literal 0 HcmV?d00001 diff --git a/src/storefront/cart.py b/src/storefront/cart.py index 65d0f28..8e6d381 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -72,7 +72,7 @@ class Cart: if update_quantity: self.cart[item['variant']]['quantity'] = item['quantity'] else: - self.cart.append(item) + self.add_or_update_item(item) # TODO: abstract this to a function that will check the max amount of item in the cart if len(self) <= 20: @@ -80,6 +80,17 @@ class Cart: else: messages.warning(request, "Cart is full: 20 items or less.") + def add_or_update_item(self, new_item): + new_item_pk = int(new_item['variant']) + for item in self: + if new_item_pk == item['variant'].pk: + if new_item['options'] == item['options']: + item['quantity'] += new_item['quantity'] + return + else: + continue + self.cart.append(new_item) + def save(self): self.session[settings.CART_SESSION_ID] = self.cart self.session.modified = True @@ -123,6 +134,9 @@ class Cart: return 0 def get_shipping_container_choices(self): + is_selectable = Q( + is_selectable=True + ) min_weight_matched = Q( min_order_weight__lte=self.get_total_weight()) | Q( min_order_weight__isnull=True @@ -132,7 +146,7 @@ class Cart: max_order_weight__isnull=True ) containers = ShippingRate.objects.filter( - min_weight_matched & max_weight_matched + is_selectable & min_weight_matched & max_weight_matched ) return containers diff --git a/src/storefront/views.py b/src/storefront/views.py index 5994b41..a332159 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -7,6 +7,7 @@ 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.cache import cache from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.http import JsonResponse, HttpResponseRedirect from django.views.generic.base import View, RedirectView, TemplateView @@ -36,7 +37,8 @@ from accounts.forms import ( ) from core.models import ( ProductCategory, Product, ProductVariant, ProductOption, - Order, Transaction, OrderLine, Coupon, ShippingRate + Order, Transaction, OrderLine, Coupon, ShippingRate, + SiteSettings ) from core.forms import ShippingRateForm from core import OrderStatus, ShippingContainer @@ -222,7 +224,7 @@ class CheckoutAddressView(FormView): if user.is_authenticated and user.default_shipping_address: address = user.default_shipping_address initial = { - 'full_name': address.first_name+' '+address.last_name, + '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, @@ -233,7 +235,7 @@ class CheckoutAddressView(FormView): elif self.request.session.get('shipping_address'): address = self.request.session.get('shipping_address') initial = { - 'full_name': address['first_name']+' '+address['last_name'], + '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'], @@ -267,6 +269,13 @@ class CheckoutShippingView(FormView): template_name = 'storefront/checkout_shipping_form.html' form_class = CheckoutShippingForm success_url = reverse_lazy('storefront:order-create') + containers = None + + def get_containers(self, request): + if self.containers is None: + cart = Cart(request) + self.containers = cart.get_shipping_container_choices() + return self.containers def get(self, request, *args, **kwargs): if not self.request.session.get('shipping_address'): @@ -274,16 +283,22 @@ class CheckoutShippingView(FormView): return HttpResponseRedirect( reverse('storefront:checkout-address') ) + site_settings = cache.get('SiteSettings') + cart = Cart(self.request) + if len(self.get_containers(request)) == 0: + self.request.session['shipping_container'] = site_settings.default_shipping_rate + return HttpResponseRedirect( + reverse('storefront:order-create') + ) return super().get(request, *args, **kwargs) def get_form(self, form_class=None): cart = Cart(self.request) - containers = cart.get_shipping_container_choices() - for container in containers: + for container in self.get_containers(self.request): container.s_cost = cart.get_shipping_cost(container.container) if form_class is None: form_class = self.get_form_class() - return form_class(containers, **self.get_form_kwargs()) + return form_class(self.get_containers(self.request), **self.get_form_kwargs()) def form_valid(self, form): shipping_container = ShippingRate.objects.get( @@ -400,7 +415,6 @@ def paypal_order_transaction_capture(request, transaction_id): transaction.save() cart.clear() logger.debug(f'\nPayPal Response data: {data}\n') - return JsonResponse(data) else: return JsonResponse({'details': 'invalid request'}) diff --git a/src/templates/dashboard.html b/src/templates/dashboard.html index 690da0b..6409bb3 100644 --- a/src/templates/dashboard.html +++ b/src/templates/dashboard.html @@ -33,6 +33,12 @@ Catalog + Orders