This commit is contained in:
Nathan Chapman 2022-05-11 17:39:22 -06:00
parent 750afa6033
commit 60896a2835
8 changed files with 204 additions and 51 deletions

View File

@ -1 +1,65 @@
[{"model": "accounts.address", "pk": 1, "fields": {"first_name": "Nathan", "last_name": "Chapman", "street_address_1": "1504 N 230 E", "street_address_2": "", "city": "North Logan", "state": "UT", "postal_code": "84341"}}, {"model": "accounts.address", "pk": 2, "fields": {"first_name": "Nathan", "last_name": "Chapman", "street_address_1": "1125 W 400 N", "street_address_2": "", "city": "Logan", "state": "UT", "postal_code": "84321"}}, {"model": "accounts.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$320000$VLksxVkUXOtoAEthHFi6DB$xj+81uh6NwPfZFP+7agf2UAJQZN89j5Wt38gUXeJ3z0=", "last_login": "2022-05-03T23:58:18.358Z", "is_superuser": true, "username": "nathanchapman", "first_name": "Nathan", "last_name": "Chapman", "email": "contact@nathanjchapman.com", "is_staff": true, "is_active": true, "date_joined": "2022-04-28T01:24:47.591Z", "default_shipping_address": 1, "default_billing_address": null, "groups": [], "user_permissions": [], "addresses": []}}, {"model": "accounts.user", "pk": 13, "fields": {"password": "pbkdf2_sha256$320000$L6WDkOMJwmkjR9OVsXfsIj$otr4goV5Tz5Hy5l24UkSYcH0L9Y5hDD89GKYD6LGcZo=", "last_login": null, "is_superuser": false, "username": "john", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "is_staff": false, "is_active": true, "date_joined": "2022-05-04T00:00:11Z", "default_shipping_address": null, "default_billing_address": null, "groups": [], "user_permissions": [], "addresses": []}}]
[{
"model": "accounts.address",
"pk": 1,
"fields": {
"first_name": "Nathan",
"last_name": "Chapman",
"street_address_1": "1504 N 230 E",
"street_address_2": "",
"city": "North Logan",
"state": "UT",
"postal_code": "84341"
}
}, {
"model": "accounts.address",
"pk": 2,
"fields": {
"first_name": "John",
"last_name": "Doe",
"street_address_1": "90415 Pollich Skyway",
"street_address_2": "",
"city": "Jaskolskiburgh",
"state": "MS",
"postal_code": "32715"
}
}, {
"model": "accounts.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$320000$VLksxVkUXOtoAEthHFi6DB$xj+81uh6NwPfZFP+7agf2UAJQZN89j5Wt38gUXeJ3z0=",
"last_login": "2022-05-03T23:58:18.358Z",
"is_superuser": true,
"username": "nathanchapman",
"first_name": "Nathan",
"last_name": "Chapman",
"email": "contact@nathanjchapman.com",
"is_staff": true,
"is_active": true,
"date_joined": "2022-04-28T01:24:47.591Z",
"default_shipping_address": 1,
"default_billing_address": null,
"groups": [],
"user_permissions": [],
"addresses": []
}
}, {
"model": "accounts.user",
"pk": 2,
"fields": {
"password": "pbkdf2_sha256$320000$L6WDkOMJwmkjR9OVsXfsIj$otr4goV5Tz5Hy5l24UkSYcH0L9Y5hDD89GKYD6LGcZo=",
"last_login": null,
"is_superuser": false,
"username": "johndoe",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"is_staff": false,
"is_active": true,
"date_joined": "2022-05-04T00:00:11Z",
"default_shipping_address": 2,
"default_billing_address": null,
"groups": [],
"user_permissions": [],
"addresses": []
}
}]

View File

@ -27,11 +27,13 @@ from .weight import WeightUnits, zero_weight
logger = logging.getLogger(__name__)
class ProductEncoder(DjangoJSONEncoder):
def default(self, obj):
logger.info(f"\n{obj}\n")
return super().default(obj)
class ProductManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
@ -39,7 +41,6 @@ class ProductManager(models.Manager):
)
class Product(models.Model):
name = models.CharField(max_length=250)
subtitle = models.CharField(max_length=250, blank=True)
@ -52,7 +53,10 @@ class Product(models.Model):
null=True,
)
weight = MeasurementField(
measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True
measurement=Weight,
unit_choices=WeightUnits.CHOICES,
blank=True,
null=True
)
visible_in_listings = models.BooleanField(default=False)
@ -105,11 +109,11 @@ class ProductPhoto(models.Model):
# img.save(self.image.path)
class Coupon(models.Model):
type = models.CharField(
max_length=20, choices=VoucherType.CHOICES, default=VoucherType.ENTIRE_ORDER
max_length=20,
choices=VoucherType.CHOICES,
default=VoucherType.ENTIRE_ORDER
)
name = models.CharField(max_length=255, null=True, blank=True)
code = models.CharField(max_length=12, unique=True, db_index=True)
@ -127,6 +131,7 @@ class Coupon(models.Model):
)
products = models.ManyToManyField(Product, blank=True)
users = models.ManyToManyField(User, blank=True)
class Meta:
ordering = ("code",)
@ -143,8 +148,6 @@ class Coupon(models.Model):
return reverse('dashboard:coupon-detail', kwargs={'pk': self.pk})
class ShippingMethod(models.Model):
name = models.CharField(max_length=100)
type = models.CharField(max_length=30, choices=ShippingMethodType.CHOICES)
@ -183,7 +186,9 @@ class Order(models.Model):
null=True
)
status = models.CharField(
max_length=32, default=OrderStatus.UNFULFILLED, choices=OrderStatus.CHOICES
max_length=32,
default=OrderStatus.UNFULFILLED,
choices=OrderStatus.CHOICES
)
billing_address = models.ForeignKey(
Address,
@ -207,7 +212,6 @@ class Order(models.Model):
on_delete=models.SET_NULL
)
coupon = models.ForeignKey(
Coupon,
related_name='orders',
@ -228,7 +232,6 @@ class Order(models.Model):
default=0
)
weight = MeasurementField(
measurement=Weight,
unit_choices=WeightUnits.CHOICES,
@ -263,7 +266,6 @@ class Order(models.Model):
ordering = ('-created_at',)
class Transaction(models.Model):
status = models.CharField(
max_length=32,
@ -342,4 +344,3 @@ class TrackingNumber(models.Model):
def __str__(self):
return self.tracking_id

View File

@ -1,9 +1,11 @@
import os, time
import os
import time
from selenium.webdriver.firefox.webdriver import WebDriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
class HomeTests(StaticLiveServerTestCase):
fixtures = ['accounts.json', 'products.json']

View File

@ -573,6 +573,11 @@ article + article {
margin-top: 8rem;
}
.error-view {
text-align: center;
margin: auto 0;
}
/* Product reviews
========================================================================== */
.review__list {

View File

@ -1,4 +1,5 @@
import logging, json
import logging
import json
from requests import ConnectionError
from django import forms
from django.conf import settings
@ -14,13 +15,19 @@ from .tasks import contact_form_email
logger = logging.getLogger(__name__)
class AddToCartForm(forms.Form):
grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES)
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
class UpdateCartItemForm(forms.Form):
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
update = forms.BooleanField(required=False, initial=True, widget=forms.HiddenInput)
update = forms.BooleanField(
required=False,
initial=True,
widget=forms.HiddenInput
)
class AddToSubscriptionForm(forms.Form):
@ -83,7 +90,6 @@ class AddressForm(forms.Form):
'Could not connect to USPS, try again.'
)
if 'Error' in validation.result['AddressValidateResponse']['Address']:
error = validation.result['AddressValidateResponse']['Address']['Error']['Description']
raise ValidationError(
@ -97,6 +103,7 @@ class AddressForm(forms.Form):
'Could not find Zip5'
)
class OrderCreateForm(forms.ModelForm):
email = forms.CharField(widget=forms.HiddenInput())
first_name = forms.CharField(widget=forms.HiddenInput())
@ -113,9 +120,11 @@ class OrderCreateForm(forms.ModelForm):
'shipping_total': forms.HiddenInput()
}
class CouponApplyForm(forms.Form):
code = forms.CharField(label='Coupon code')
class ContactForm(forms.Form):
GOOGLE = 'Google Search'
SHOP = 'The coffee shop'

View File

@ -16,15 +16,16 @@ from storefront.cart import Cart
logger = logging.getLogger(__name__)
class CartTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
self.customer = User.objects.create_user(
username='petertempler', email='peter@testing.com', password='peterspassword321'
class CartTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.customer = User.objects.create_user(
username='petertempler',
email='peter@testing.com',
password='peterspassword321'
)
self.product = Product.objects.create(
cls.product = Product.objects.create(
name='Dante\'s Tornado',
description='Coffee',
sku='23987',
@ -32,11 +33,15 @@ class CartTest(TestCase):
weight=Weight(oz=16),
visible_in_listings=True
)
self.order = Order.objects.create(
customer=self.customer,
cls.order = Order.objects.create(
customer=cls.customer,
total_net_amount=13.4
)
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
self.client.force_login(self.customer)
self.client.session['shipping_address'] = {
'first_name': 'Nathan',
@ -89,7 +94,10 @@ class CartTest(TestCase):
update_quantity=False
)
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 1)
self.assertEqual(
cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'],
1
)
self.assertEqual(len(cart), 1)
self.assertEqual(sum(cart.get_item_prices()), Decimal('13.4'))
self.assertEqual(cart.get_total_price(), Decimal('13.4'))
@ -100,7 +108,10 @@ class CartTest(TestCase):
grind=CoffeeGrind.WHOLE,
update_quantity=False
)
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 2)
self.assertEqual(
cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'],
2
)
self.assertEqual(len(cart), 2)
cart.add(
@ -110,7 +121,10 @@ class CartTest(TestCase):
grind=CoffeeGrind.ESPRESSO,
update_quantity=False
)
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.ESPRESSO]['quantity'], 3)
self.assertEqual(
cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.ESPRESSO]['quantity'],
3
)
self.assertEqual(len(cart), 5)
self.assertEqual(cart.get_total_price(), Decimal('67'))
@ -128,7 +142,10 @@ class CartTest(TestCase):
grind=CoffeeGrind.WHOLE,
update_quantity=False
)
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 3)
self.assertEqual(
cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'],
3
)
cart.add(
request,
@ -137,7 +154,10 @@ class CartTest(TestCase):
grind=CoffeeGrind.WHOLE,
update_quantity=True
)
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 1)
self.assertEqual(
cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'],
1
)
def test_cart_remove_item(self):
cart_detail_url = reverse('storefront:cart-detail')
@ -172,6 +192,3 @@ class CartTest(TestCase):
update_quantity=False
)
self.assertEqual(cart.get_total_weight(), Decimal(48))
def test_cart_(self):
pass

View File

@ -21,9 +21,9 @@ from . import RequestFaker
logger = logging.getLogger(__name__)
class CreateOrderTest(TestCase):
def setUp(self):
self.client = Client()
self.product = Product.objects.create(
@classmethod
def setUpTestData(cls):
cls.product = Product.objects.create(
name='Decaf',
description='Coffee',
sku='23987',
@ -32,6 +32,10 @@ class CreateOrderTest(TestCase):
visible_in_listings=True
)
def setUp(self):
self.client = Client()
def test_build_request_body(self):
product_list_url = reverse('storefront:product-list')
response = self.client.get(product_list_url, follow=True)

View File

@ -6,10 +6,12 @@ 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.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.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
@ -25,17 +27,23 @@ 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 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 .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'
@ -53,6 +61,7 @@ class CartView(TemplateView):
context['coupon_apply_form'] = CouponApplyForm()
return context
class CartAddProductView(SingleObjectMixin, FormView):
model = Product
form_class = AddToCartForm
@ -118,7 +127,7 @@ class CouponApplyView(FormView):
def form_valid(self, form):
today = timezone.localtime(timezone.now()).date()
code = form.cleaned_data['code']
code = form.cleaned_data['code'].upper()
try:
coupon = Coupon.objects.get(
code__iexact=code,
@ -200,6 +209,7 @@ class CheckoutAddressView(FormView):
self.request.session['shipping_address'] = address
return super().form_valid(form)
class OrderCreateView(CreateView):
model = Order
template_name = 'storefront/order_form.html'
@ -260,10 +270,11 @@ class OrderCreateView(CreateView):
return JsonResponse(data)
@csrf_exempt
@require_POST
def paypal_order_transaction_capture(request, transaction_id):
if request.method =="POST":
if request.method == "POST":
data = CaptureOrder().capture_order(transaction_id)
cart = Cart(request)
cart.clear()
@ -280,6 +291,7 @@ def paypal_order_transaction_capture(request, transaction_id):
else:
return JsonResponse({'details': 'invalid request'})
@csrf_exempt
@require_POST
def paypal_webhook_endpoint(request):
@ -291,39 +303,66 @@ def paypal_webhook_endpoint(request):
class PaymentDoneView(TemplateView):
template_name = 'storefront/payment_done.html'
class PaymentCanceledView(TemplateView):
template_name = 'storefront/payment_canceled.html'
class CustomerDetailView(LoginRequiredMixin, DetailView):
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
class CustomerUpdateView(LoginRequiredMixin, UpdateView):
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})
return reverse(
'storefront:customer-detail', kwargs={'pk': self.object.pk}
)
class OrderDetailView(LoginRequiredMixin, DetailView):
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(LoginRequiredMixin, CreateView):
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)
@ -338,13 +377,23 @@ class CustomerAddressCreateView(LoginRequiredMixin, CreateView):
return super().form_valid(form)
def get_success_url(self):
return reverse('storefront:customer-detail', kwargs={'pk': self.kwargs['pk']})
return reverse(
'storefront:customer-detail', kwargs={'pk': self.kwargs['pk']}
)
class CustomerAddressUpdateView(LoginRequiredMixin, UpdateView):
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)
@ -355,16 +404,18 @@ class CustomerAddressUpdateView(LoginRequiredMixin, UpdateView):
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