Merge branch 'feature/user-permissions' into develop

This commit is contained in:
Nathan Chapman 2022-05-11 20:09:52 -06:00
commit 37ef9d3928
19 changed files with 469 additions and 61 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

@ -0,0 +1,29 @@
[{
"model": "core.coupon",
"pk": 1,
"fields": {
"type": "entire_order",
"name": "Save 10%: Valid",
"code": "MAY2022",
"valid_from": "2022-05-01T06:00:00Z",
"valid_to": "2022-05-31T06:00:00Z",
"discount_value_type": "percentage",
"discount_value": "10.00",
"products": [],
"users": [1]
}
}, {
"model": "core.coupon",
"pk": 2,
"fields": {
"type": "entire_order",
"name": "Save 10%: Invalid",
"code": "APR2022",
"valid_from": "2022-04-01T06:00:00Z",
"valid_to": "2022-04-30T06:00:00Z",
"discount_value_type": "percentage",
"discount_value": "10.00",
"products": [],
"users": []
}
}]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.0.2 on 2022-05-11 00:55
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('core', '0008_alter_order_coupon_alter_order_weight'),
]
operations = [
migrations.AddField(
model_name='coupon',
name='users',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
),
]

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

@ -54,7 +54,3 @@ class AddressTests(StaticLiveServerTestCase):
).text,
'USPS: Address Not Found.'
)

View File

@ -0,0 +1,100 @@
import os
import time
import logging
from django.test import TestCase, Client
from django.conf import settings
from selenium.webdriver.firefox.webdriver import WebDriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
logger = logging.getLogger(__name__)
class CouponTests(StaticLiveServerTestCase):
fixtures = ['products.json', 'accounts.json', 'coupons.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = WebDriver()
@classmethod
def tearDownClass(cls):
cls.browser.quit()
super().tearDownClass()
def login(self):
self.browser.get('%s%s' % (self.live_server_url, '/accounts/login/'))
username_input = self.browser.find_element_by_name("login")
username_input.send_keys('john@example.com')
password_input = self.browser.find_element_by_name("password")
password_input.send_keys('Bf25XBdP4vdt2X9L')
self.browser.find_element_by_xpath('//input[@value="Login"]').click()
def test_driver_has_session(self):
self.browser.get(self.live_server_url)
session_id = self.browser.get_cookie('sessionid')
self.assertTrue(session_id)
def test_apply_coupon_to_order(self):
# Add item to cart
self.browser.get(self.live_server_url + '/products/1/')
self.browser.find_element_by_xpath(
'//input[@value="Add to cart"]'
).click()
self.assertEqual(
self.browser.find_element_by_class_name('cart__count').text,
'1'
)
# Add coupon code
coupon_input = self.browser.find_element_by_id('id_code')
coupon_input.send_keys('MAY2022')
self.browser.find_element_by_xpath('//input[@value="Apply"]').click()
self.browser.find_element_by_xpath(
'//a[contains(text(), "Proceed to Checkout")]'
).click()
# Add address
self.assertEqual(
self.browser.title,
'Checkout | Port Townsend Roasting Co.'
)
full_name_input = self.browser.find_element_by_name("full_name")
full_name_input.send_keys('John Doe')
email_input = self.browser.find_element_by_id('id_email')
email_input.send_keys('contact@nathanjchapman.com')
street_address_1_input = self.browser.find_element_by_name(
'street_address_1'
)
street_address_1_input.send_keys('1579 Talon Dr')
city_input = self.browser.find_element_by_name('city')
city_input.send_keys('Logan')
state_select = select = Select(
self.browser.find_element_by_name('state')
)
state_select.select_by_value('UT')
postal_code_input = self.browser.find_element_by_name('postal_code')
postal_code_input.send_keys('84321')
self.browser.find_element_by_xpath(
'//input[@value="Continue to Payment"]'
).click()
self.assertEqual(
self.browser.title,
'Checkout | Port Townsend Roasting Co.'
)
message_text = self.browser.find_element_by_css_selector(
'.messages p'
).text
self.assertEqual(
'Coupon already used.',
message_text
)

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

@ -24,6 +24,7 @@ from .payments import CreateOrder
logger = logging.getLogger(__name__)
class Cart:
def __init__(self, request):
self.request = request
@ -34,7 +35,9 @@ class Cart:
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, request, product, quantity=1, grind='', update_quantity=False):
def add(
self, request, product, quantity=1, grind='', update_quantity=False
):
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {

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',
@ -56,7 +61,6 @@ class CartTest(TestCase):
request = response.wsgi_request
cart = Cart(request)
cart = Cart(request)
cart.add(
request,
product=self.product,
@ -89,7 +93,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 +107,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 +120,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 +141,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 +153,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 +191,3 @@ class CartTest(TestCase):
update_quantity=False
)
self.assertEqual(cart.get_total_weight(), Decimal(48))
def test_cart_(self):
pass

View File

@ -0,0 +1,18 @@
import logging
from decimal import Decimal
from django.test import TestCase, Client, RequestFactory
from django.urls import reverse
from django.conf import settings
from measurement.measures import Weight
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
from accounts.models import User, Address
from core.models import Product, Order
from core import CoffeeGrind
from storefront.forms import AddressForm, OrderCreateForm
from storefront.views import OrderCreateView, CheckoutAddressView
from storefront.cart import Cart
logger = logging.getLogger(__name__)

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

@ -9,7 +9,7 @@ from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
from accounts.models import User, Address
from core.models import Product, Order
from core.models import Product, Order, Coupon
from core import CoffeeGrind
from storefront.forms import AddressForm, OrderCreateForm
from storefront.views import OrderCreateView, CheckoutAddressView
@ -17,7 +17,8 @@ from storefront.cart import Cart
logger = logging.getLogger(__name__)
class CheckoutAddressViewTest(TestCase):
class CheckoutAddressViewTests(TestCase):
def setUp(self):
self.client = Client()
@ -30,3 +31,48 @@ class CheckoutAddressViewTest(TestCase):
response = self.client.get(reverse('storefront:checkout-address'))
self.assertTrue(response.context['form'])
self.assertTrue(isinstance(response.context['form'], AddressForm))
class OrderCreateViewTests(TestCase):
fixtures = ['accounts.json', 'coupons.json']
@classmethod
def setUpTestData(cls):
cls.customer = User.objects.get(pk=1)
cls.product = Product.objects.create(
name="Dante's Tornado",
description='Coffee',
sku='23987',
price=13.4,
weight=Weight(oz=16),
visible_in_listings=True
)
cls.order = Order.objects.create(
customer=cls.customer,
total_net_amount=13.4
)
def setUp(self):
self.client = Client()
def test_used_coupon_creates_error_on_checkout(self):
session = self.client.session
session['shipping_address'] = {
'first_name': 'Nathan',
'last_name': 'Chapman',
'email': 'contact@nathanjchapman.com',
'street_address_1': '1504 N 230 E',
'street_address_2': '',
'city': 'North Logan',
'state': 'UT',
'postal_code': '84341'
}
session['coupon_code'] = 'MAY2022'
session.save()
response = self.client.get(
reverse('storefront:order-create'), follow=True
)
self.assertTrue(self.client.session.get('shipping_address'))
self.assertTemplateUsed(response, 'storefront/order_form.html')
self.assertContains(response, 'Coupon already used', status_code=200)

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'
@ -212,8 +222,17 @@ class OrderCreateView(CreateView):
return HttpResponseRedirect(
reverse('storefront:checkout-address')
)
else:
return super().get(request, *args, **kwargs)
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')
)
user = get_object_or_404(User, email=address['email'])
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)
@ -249,6 +268,13 @@ class OrderCreateView(CreateView):
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
coupon = get_object_or_404(
Coupon,
code=self.request.session.get('coupon_code')
)
if coupon:
form.instance.coupon = coupon
coupon.users.add(form.instance.customer)
self.object = form.save()
bulk_list = cart.build_bulk_list(self.object)
objs = OrderLine.objects.bulk_create(bulk_list)
@ -260,10 +286,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 +307,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 +319,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 +393,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 +420,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

7
src/templates/400.html Normal file
View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<article class="error-view">
<h1>400 Bad request</h1>
</article>
{% endblock %}

7
src/templates/403.html Normal file
View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<article class="error-view">
<h1>403 Forbidden</h1>
</article>
{% endblock %}

7
src/templates/404.html Normal file
View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<article class="error-view">
<h1>404 Page not found</h1>
</article>
{% endblock %}

7
src/templates/500.html Normal file
View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<article class="error-view">
<h1>500 Server error</h1>
</article>
{% endblock %}