From 6141468f2161c0bd8252b193cf8ac85d9a622c61 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 1 Nov 2022 18:07:54 -0600 Subject: [PATCH 01/11] Fix functional tests --- src/core/fixtures/shipping_rates.json | 19 +++++++++++++++++++ src/functional_tests/test_address.py | 2 +- src/functional_tests/test_coupon.py | 13 +++++++++---- src/ptcoffee/settings.py | 15 ++++++++++++--- 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/core/fixtures/shipping_rates.json diff --git a/src/core/fixtures/shipping_rates.json b/src/core/fixtures/shipping_rates.json new file mode 100644 index 0000000..17fbc42 --- /dev/null +++ b/src/core/fixtures/shipping_rates.json @@ -0,0 +1,19 @@ +[{ + "model": "core.shippingrate", + "pk": 1, + "fields": { + "shipping_provider": "USPS", + "name": "Variable", + "container": "VARIABLE", + "min_order_weight": null, + "max_order_weight": null, + "is_selectable": false + } +}, { + "model": "core.sitesettings", + "pk": 1, + "fields": { + "usps_user_id": "012BETTE1249", + "default_shipping_rate": 1 + } +}] diff --git a/src/functional_tests/test_address.py b/src/functional_tests/test_address.py index ba603b5..aa990c4 100644 --- a/src/functional_tests/test_address.py +++ b/src/functional_tests/test_address.py @@ -12,7 +12,7 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase class AddressTests(StaticLiveServerTestCase): - fixtures = ['products.json'] + fixtures = ['shipping_rates.json', 'products.json'] @classmethod def setUpClass(cls): diff --git a/src/functional_tests/test_coupon.py b/src/functional_tests/test_coupon.py index 226109a..4029d09 100644 --- a/src/functional_tests/test_coupon.py +++ b/src/functional_tests/test_coupon.py @@ -19,7 +19,9 @@ logger = logging.getLogger(__name__) class CouponTests(StaticLiveServerTestCase): - fixtures = ['products.json', 'accounts.json', 'coupons.json'] + fixtures = [ + 'shipping_rates.json', 'products.json', 'accounts.json', 'coupons.json' + ] @classmethod def setUpClass(cls): @@ -87,7 +89,8 @@ class CouponTests(StaticLiveServerTestCase): 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, + self.browser.find_element( + By.XPATH, '//input[@value="Continue"]' ).click() @@ -106,7 +109,8 @@ class CouponTests(StaticLiveServerTestCase): def test_apply_used_coupon_to_order_returns_message(self): # Add item to cart self.browser.get(self.live_server_url + '/products/1/') - self.browser.find_element(By.XPATH, + self.browser.find_element( + By.XPATH, '//input[@value="Add to cart"]' ).click() self.assertEqual( @@ -118,7 +122,8 @@ class CouponTests(StaticLiveServerTestCase): 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, + self.browser.find_element( + By.XPATH, '//a[contains(text(), "Proceed to Checkout")]' ).click() diff --git a/src/ptcoffee/settings.py b/src/ptcoffee/settings.py index 39b2ebf..70c3c49 100644 --- a/src/ptcoffee/settings.py +++ b/src/ptcoffee/settings.py @@ -99,9 +99,18 @@ WSGI_APPLICATION = 'ptcoffee.wsgi.application' # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases -DATABASES = { - 'default': DATABASE_CONFIG -} +if DEBUG: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'USER': 'django', + 'NAME': 'ptcoffee_dev', + }, + } +else: + DATABASES = { + 'default': DATABASE_CONFIG + } CACHES = {'default': CACHE_CONFIG} From e14aef104031f86a2d13398c9c0953525ef4984d Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 1 Nov 2022 18:10:28 -0600 Subject: [PATCH 02/11] Fix core test --- src/core/tests/test_models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/tests/test_models.py b/src/core/tests/test_models.py index 841f14f..d3df44f 100644 --- a/src/core/tests/test_models.py +++ b/src/core/tests/test_models.py @@ -23,10 +23,10 @@ class ProductModelTest(TestCase): Product.objects.create( name='Pantomime', subtitle='Very Dark French Roast', - description='Our darkest drip. A blend of five different beans roasted two ways. Organic Africa, Indonesia, and South and Central America.', - sku='565656', - price=Decimal('15.00'), - weight=Weight(oz=16), + description='Our darkest drip. A blend of five different \ + beans roasted two ways. Organic Africa, Indonesia, and \ + South and Central America.', + checkout_limit=10, visible_in_listings=True, sorting=1, ) From bfeb3718f5dfb3e0b7250ff6bb63a2d93eab4993 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 1 Nov 2022 18:14:13 -0600 Subject: [PATCH 03/11] Update database settings and config for testing --- src/ptcoffee/config.py | 20 ++++++++++++++------ src/ptcoffee/settings.py | 15 +++------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/ptcoffee/config.py b/src/ptcoffee/config.py index 8701bbc..8d6e813 100644 --- a/src/ptcoffee/config.py +++ b/src/ptcoffee/config.py @@ -5,13 +5,21 @@ load_dotenv() DEBUG = os.environ.get('DEBUG', 'True') == 'True' -DATABASE_CONFIG = { - 'ENGINE': 'django.db.backends.postgresql', - 'OPTIONS': { - 'service': 'pg_service', - 'passfile': '.pgpass' +if DEBUG: + DATABASE_CONFIG = { + 'ENGINE': 'django.db.backends.postgresql', + 'USER': 'django', + 'NAME': 'ptcoffee_dev' } -} +else: + DATABASE_CONFIG = { + 'ENGINE': 'django.db.backends.postgresql', + 'OPTIONS': { + 'service': 'pg_service', + 'passfile': '.pgpass' + } + } + SECRET_KEY = os.environ.get('SECRET_KEY', '') CACHE_CONFIG = { 'LOCATION': 'redis://127.0.0.1:6379', diff --git a/src/ptcoffee/settings.py b/src/ptcoffee/settings.py index 70c3c49..39b2ebf 100644 --- a/src/ptcoffee/settings.py +++ b/src/ptcoffee/settings.py @@ -99,18 +99,9 @@ WSGI_APPLICATION = 'ptcoffee.wsgi.application' # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases -if DEBUG: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'USER': 'django', - 'NAME': 'ptcoffee_dev', - }, - } -else: - DATABASES = { - 'default': DATABASE_CONFIG - } +DATABASES = { + 'default': DATABASE_CONFIG +} CACHES = {'default': CACHE_CONFIG} From d0621b2d471b82b1b185ac29510e39cfb907e33b Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 1 Nov 2022 18:26:23 -0600 Subject: [PATCH 04/11] Fix product-create "cancel" link to redirect to catalog --- .../dashboard/product_create_form.html | 2 +- src/dashboard/tests/test_views.py | 60 ++++++++++++++++++- src/dashboard/views.py | 16 ++--- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/dashboard/templates/dashboard/product_create_form.html b/src/dashboard/templates/dashboard/product_create_form.html index 4716353..0f6e4c4 100644 --- a/src/dashboard/templates/dashboard/product_create_form.html +++ b/src/dashboard/templates/dashboard/product_create_form.html @@ -10,7 +10,7 @@ {% csrf_token %} {{form.as_p}}

- or cancel + or cancel

diff --git a/src/dashboard/tests/test_views.py b/src/dashboard/tests/test_views.py index b054ba7..a0b06d1 100644 --- a/src/dashboard/tests/test_views.py +++ b/src/dashboard/tests/test_views.py @@ -20,8 +20,12 @@ from dashboard.forms import ( from dashboard.views import ( DashboardHomeView, DashboardConfigView, - ShippingRateCreateView, + CatalogView, + StockView, ShippingRateDetailView, + ShippingRateCreateView, + ShippingRateUpdateView, + ShippingRateDeleteView, CouponListView, CouponCreateView, CouponDetailView, @@ -30,24 +34,74 @@ from dashboard.views import ( OrderListView, OrderDetailView, OrderFulfillView, + OrderCancelView, OrderTrackingView, + CategoryListView, + CategoryCreateView, + CategoryDetailView, + CategoryUpdateView, + CategoryDeleteView, ProductListView, ProductDetailView, - ProductUpdateView, ProductCreateView, + ProductUpdateView, ProductDeleteView, ProductPhotoCreateView, ProductPhotoDeleteView, + ProductVariantCreateView, + ProductVariantUpdateView, + ProductVariantDeleteView, + ProductVariantStockUpdateView, + ProductOptionDetailView, + ProductOptionCreateView, + ProductOptionUpdateView, + ProductOptionDeleteView, CustomerListView, CustomerDetailView, - CustomerUpdateView + CustomerUpdateView, ) logger = logging.getLogger(__name__) +class ProductCreateViewTests(TestCase): + fixtures = [ + 'shipping_rates.json', + 'accounts.json', + 'coupons.json', + 'products.json', + 'orders.json' + ] + + @classmethod + def setUpTestData(cls): + cls.admin_user = User.objects.get(pk=1) + + def setUp(self): + self.client = Client() + self.client.force_login(self.admin_user) + + def test_view_url_exists_at_desired_location(self): + response = self.client.get('/dashboard/products/new/') + self.assertEqual(response.status_code, 200) + + def test_view_url_accesible_by_name(self): + response = self.client.get( + reverse('dashboard:product-create') + ) + self.assertEqual(response.status_code, 200) + + def test_view_uses_correct_template(self): + response = self.client.get( + reverse('dashboard:product-create') + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'dashboard/product_create_form.html') + + class OrderCancelViewTests(TestCase): fixtures = [ + 'shipping_rates.json', 'accounts.json', 'coupons.json', 'products.json', diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 1ceb8cd..47030b1 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -346,13 +346,6 @@ class ProductDetailView(LoginRequiredMixin, DetailView): return obj -class ProductUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): - model = Product - template_name = 'dashboard/product_update_form.html' - fields = '__all__' - success_message = '%(name)s saved.' - - class ProductCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = Product template_name = 'dashboard/product_create_form.html' @@ -360,10 +353,17 @@ class ProductCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): success_message = '%(name)s created.' +class ProductUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): + model = Product + template_name = 'dashboard/product_update_form.html' + fields = '__all__' + success_message = '%(name)s saved.' + + class ProductDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Product template_name = 'dashboard/product_confirm_delete.html' - success_url = reverse_lazy('dashboard:product-list') + success_url = reverse_lazy('dashboard:catalog') success_message = 'Product deleted.' From 4955bf8dd9b8581f2cc7d4994609f7e7837d3f13 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 1 Nov 2022 18:33:35 -0600 Subject: [PATCH 05/11] Update coupon ordering --- .../migrations/0003_alter_coupon_options.py | 17 +++++++++++++++++ .../migrations/0004_alter_coupon_options.py | 17 +++++++++++++++++ src/core/models.py | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/core/migrations/0003_alter_coupon_options.py create mode 100644 src/core/migrations/0004_alter_coupon_options.py diff --git a/src/core/migrations/0003_alter_coupon_options.py b/src/core/migrations/0003_alter_coupon_options.py new file mode 100644 index 0000000..c1cd2e1 --- /dev/null +++ b/src/core/migrations/0003_alter_coupon_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.2 on 2022-11-02 00:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_shippingrate_is_selectable_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='coupon', + options={'ordering': ['valid_from', 'valid_to', 'code']}, + ), + ] diff --git a/src/core/migrations/0004_alter_coupon_options.py b/src/core/migrations/0004_alter_coupon_options.py new file mode 100644 index 0000000..b849b55 --- /dev/null +++ b/src/core/migrations/0004_alter_coupon_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.2 on 2022-11-02 00:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_alter_coupon_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='coupon', + options={'ordering': ['-valid_from', '-valid_to', 'code']}, + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 3e290e7..a9a32d4 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -226,7 +226,7 @@ class Coupon(models.Model): users = models.ManyToManyField(User, blank=True) class Meta: - ordering = ("code",) + ordering = ['-valid_from', '-valid_to', 'code'] def __str__(self): return self.name From 6c8abec2d51321acef8c999f6f87c16b392cff9b Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 5 Nov 2022 11:27:13 -0600 Subject: [PATCH 06/11] Add free shipping setting --- src/core/fixtures/merchandise.json | 369 ++++++++++++++++++ src/core/fixtures/new_coffee_variants.json | 271 +++++++++++++ .../0005_sitesettings_free_shipping_min.py | 18 + .../0006_productvariant_sorting_and_more.py | 23 ++ .../0007_alter_productvariant_options.py | 17 + .../migrations/0008_productvariant_image.py | 19 + .../templates/dashboard/settings_form.html | 18 + src/dashboard/views.py | 12 +- src/static/scripts/product_list.js | 8 +- src/storefront/cart.py | 18 +- .../templates/storefront/order_detail.html | 2 +- .../templates/storefront/order_form.html | 6 +- src/storefront/views.py | 12 +- 13 files changed, 785 insertions(+), 8 deletions(-) create mode 100644 src/core/fixtures/merchandise.json create mode 100644 src/core/fixtures/new_coffee_variants.json create mode 100644 src/core/migrations/0005_sitesettings_free_shipping_min.py create mode 100644 src/core/migrations/0006_productvariant_sorting_and_more.py create mode 100644 src/core/migrations/0007_alter_productvariant_options.py create mode 100644 src/core/migrations/0008_productvariant_image.py create mode 100644 src/dashboard/templates/dashboard/settings_form.html diff --git a/src/core/fixtures/merchandise.json b/src/core/fixtures/merchandise.json new file mode 100644 index 0000000..95338a8 --- /dev/null +++ b/src/core/fixtures/merchandise.json @@ -0,0 +1,369 @@ +[{ + "model": "core.product", + "pk": 10, + "fields": { + "category": 2, + "name": "BLTC Mug", + "subtitle": "Camper Vacuum Mug", + "description": "Camp mug is made of double-wall stainless steel, designed to keep drinks hot or cold for hours.", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 10, + "name": "Blue - 12 oz.", + "sku": "REPLACE_4001", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 10, + "image": "products/images/bltc_mug_blue_12_oz.jpg" + } +}, + + +{ + "model": "core.product", + "pk": 11, + "fields": { + "category": 2, + "name": "BLTC Pull-over Hoodie", + "subtitle": "Classic BLTC Logo front left and Steam/Waves on back", + "description": "ComfortBlend Hoodie - Screen. EcoSmart® 7.8-oz poly-cotton blend hoodie features a front pouch pocket and two-ply hood with matching drawcord. Rib-knit cuffs and waistband ensure all-day comfort and warmth.", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 11, + "name": "Blue", + "sku": "REPLACE_4002", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 11, + "image": "products/images/bltc_pullover_hoodie_blue_front.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 11, + "image": "products/images/bltc_pullover_hoodie_blue_back.jpg" + } +}, + + +{ + "model": "core.product", + "pk": 12, + "fields": { + "category": 2, + "name": "BLTC T-Shirt", + "subtitle": "Logo T-Shirt with Steam/Waves on back", + "description": "5.5 oz. DryBlend 50/50 T-Shirt - Screen. The 5.5-oz 50/50 cotton/polyester blend fabric is moisture-wicking.", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 12, + "name": "Heather Red", + "sku": "REPLACE_4003", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 12, + "name": "Heather Blue", + "sku": "REPLACE_4004", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 12, + "image": "products/images/bltc_tshirt_blue_front.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 12, + "image": "products/images/bltc_tshirt_blue_back.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 12, + "image": "products/images/bltc_tshirt_red_front.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 12, + "image": "products/images/bltc_tshirt_red_back.jpg" + } +}, + + +{ + "model": "core.product", + "pk": 13, + "fields": { + "category": 2, + "name": "BLTC Trucker Cap", + "subtitle": "Washed Cotton Mesh Back Cap with Logo", + "description": "Soft washed cotton gives this trucker hat an appealing vintage look. Designed with ventilated mesh back panels and a front twill sweatband for comfort.", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 13, + "name": "Black and White", + "sku": "REPLACE_4005", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 13, + "image": "products/images/bltc_trucker_cap_black_and_white.jpg" + } +}, + + +{ + "model": "core.product", + "pk": 14, + "fields": { + "category": 2, + "name": "BLTC Zip-up Hoodie", + "subtitle": "Classic BLTC Cafe Logo without the extra branding", + "description": "Made from a 7.1-oz, 52/48 cotton/polyester blend. Includes a spacious hood with drawcords and metal grommets. Interior phone pocket with headphone cord port.", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 14, + "name": "Forest Green/Silver Embroidered", + "sku": "REPLACE_4006", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 14, + "name": "Navy Blue/Silver Embroidered", + "sku": "REPLACE_4007", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 14, + "name": "Black/Gold Embroidered", + "sku": "REPLACE_4008", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 14, + "image": "products/images/bltc_zipup_hoodie_black_gold.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 14, + "image": "products/images/bltc_zipup_hoodie_green_silver.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 14, + "image": "products/images/bltc_zipup_hoodie_navy_silver.jpg" + } +}, + + +{ + "model": "core.product", + "pk": 15, + "fields": { + "category": 2, + "name": "PT Coffee Cap", + "subtitle": "Embroidered Buttonless Cap", + "description": "These caps feature a buttonless design that won't interfere with headsets/headphones. The low profile fit and adjustable fabric strap with hook-and-loop closure helps create a more comfortable fit.", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 15, + "name": "Navy Blue with Red Embroidered Print", + "sku": "REPLACE_4009", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 15, + "image": "products/images/pt_coffee_cap_navy.jpg" + } +}, + + +{ + "model": "core.product", + "pk": 16, + "fields": { + "category": 2, + "name": "PT Coffee Cup", + "subtitle": "Bean/Leaf Logo Neo Vacuum Insulated Cup", + "description": "Designed to keep drinks hot or cold for hours. A great cup for enjoying wine, tea, coffee, hot chocolate, and more!", + "checkout_limit": 20, + "visible_in_listings": false, + "sorting": null, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 16, + "name": "Black and Gold 10 oz.", + "sku": "REPLACE_4010", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": null, + "fields": { + "product": 16, + "name": "White and Black 10 oz.", + "sku": "REPLACE_4011", + "price": null, + "weight": "00:oz", + "track_inventory": true, + "stock": 0, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 16, + "image": "products/images/pt_coffee_cup_black_gold_10_oz.jpg" + } +}, { + "model": "core.productphoto", + "pk": null, + "fields": { + "product": 16, + "image": "products/images/pt_coffee_cup_white_black_10_oz.jpg" + } +}] diff --git a/src/core/fixtures/new_coffee_variants.json b/src/core/fixtures/new_coffee_variants.json new file mode 100644 index 0000000..af5ad89 --- /dev/null +++ b/src/core/fixtures/new_coffee_variants.json @@ -0,0 +1,271 @@ +[{ + "model": "core.productvariant", + "pk": 10, + "fields": { + "product": 1, + "name": "12 oz", + "sku": "REPLACE_87431", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 11, + "fields": { + "product": 1, + "name": "5 lb", + "sku": "REPLACE_87430", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 12, + "fields": { + "product": 2, + "name": "12 oz", + "sku": "REPLACE_87429", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 13, + "fields": { + "product": 2, + "name": "5 lb", + "sku": "REPLACE_87428", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 14, + "fields": { + "product": 3, + "name": "12 oz", + "sku": "REPLACE_87427", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 15, + "fields": { + "product": 3, + "name": "5 lb", + "sku": "REPLACE_87426", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 16, + "fields": { + "product": 4, + "name": "12 oz", + "sku": "REPLACE_87425", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 17, + "fields": { + "product": 4, + "name": "5 lb", + "sku": "REPLACE_87424", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 18, + "fields": { + "product": 5, + "name": "12 oz", + "sku": "REPLACE_87423", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 19, + "fields": { + "product": 5, + "name": "5 lb", + "sku": "REPLACE_87422", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 20, + "fields": { + "product": 6, + "name": "12 oz", + "sku": "REPLACE_87421", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 21, + "fields": { + "product": 6, + "name": "5 lb", + "sku": "REPLACE_87420", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 22, + "fields": { + "product": 7, + "name": "12 oz", + "sku": "REPLACE_87419", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 23, + "fields": { + "product": 7, + "name": "5 lb", + "sku": "REPLACE_87418", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 24, + "fields": { + "product": 8, + "name": "12 oz", + "sku": "REPLACE_87417", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 25, + "fields": { + "product": 8, + "name": "5 lb", + "sku": "REPLACE_87416", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 26, + "fields": { + "product": 9, + "name": "12 oz", + "sku": "REPLACE_87467", + "price": "12.00", + "weight": "12.0:oz", + "track_inventory": false, + "stock": null, + "sorting": 1, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}, { + "model": "core.productvariant", + "pk": 27, + "fields": { + "product": 9, + "name": "5 lb", + "sku": "REPLACE_87486", + "price": "75.00", + "weight": "5.0:lb", + "track_inventory": false, + "stock": null, + "sorting": 3, + "created_at": "2022-02-23T18:06:57.624Z", + "updated_at": "2022-02-23T18:06:57.624Z" + } +}] diff --git a/src/core/migrations/0005_sitesettings_free_shipping_min.py b/src/core/migrations/0005_sitesettings_free_shipping_min.py new file mode 100644 index 0000000..8aad562 --- /dev/null +++ b/src/core/migrations/0005_sitesettings_free_shipping_min.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.2 on 2022-11-05 00:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_alter_coupon_options'), + ] + + operations = [ + migrations.AddField( + model_name='sitesettings', + name='free_shipping_min', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True), + ), + ] diff --git a/src/core/migrations/0006_productvariant_sorting_and_more.py b/src/core/migrations/0006_productvariant_sorting_and_more.py new file mode 100644 index 0000000..09208fe --- /dev/null +++ b/src/core/migrations/0006_productvariant_sorting_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.2 on 2022-11-05 15:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_sitesettings_free_shipping_min'), + ] + + operations = [ + migrations.AddField( + model_name='productvariant', + name='sorting', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='sitesettings', + name='free_shipping_min', + field=models.DecimalField(blank=True, decimal_places=2, help_text='Minimum dollar amount in the cart subtotal to qualify for free shipping', max_digits=12, null=True), + ), + ] diff --git a/src/core/migrations/0007_alter_productvariant_options.py b/src/core/migrations/0007_alter_productvariant_options.py new file mode 100644 index 0000000..0b0d344 --- /dev/null +++ b/src/core/migrations/0007_alter_productvariant_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.2 on 2022-11-05 16:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_productvariant_sorting_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='productvariant', + options={'ordering': ['sorting', 'weight']}, + ), + ] diff --git a/src/core/migrations/0008_productvariant_image.py b/src/core/migrations/0008_productvariant_image.py new file mode 100644 index 0000000..86c1278 --- /dev/null +++ b/src/core/migrations/0008_productvariant_image.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.2 on 2022-11-05 17:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0007_alter_productvariant_options'), + ] + + operations = [ + migrations.AddField( + model_name='productvariant', + name='image', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='core.productphoto'), + ), + ] diff --git a/src/dashboard/templates/dashboard/settings_form.html b/src/dashboard/templates/dashboard/settings_form.html new file mode 100644 index 0000000..a043408 --- /dev/null +++ b/src/dashboard/templates/dashboard/settings_form.html @@ -0,0 +1,18 @@ +{% extends "dashboard.html" %} + +{% block content %} +
+
+

Update Site Settings

+
+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 47030b1..7f145c6 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -37,7 +37,8 @@ from core.models import ( ShippingRate, Transaction, TrackingNumber, - Coupon + Coupon, + SiteSettings ) from core import ( @@ -88,6 +89,15 @@ class DashboardConfigView(TemplateView): return context +class SiteSettingsUpdateView(UpdateView): + model = SiteSettings + context_object_name = 'settings' + template_name = 'dashboard/settings_form.html' + fields = '__all__' + success_url = reverse_lazy('dashboard:config') + success_message = 'Settings saved.' + + class CatalogView(ListView): model = ProductCategory context_object_name = 'category_list' diff --git a/src/static/scripts/product_list.js b/src/static/scripts/product_list.js index 3f3619b..cf238cd 100644 --- a/src/static/scripts/product_list.js +++ b/src/static/scripts/product_list.js @@ -9,9 +9,13 @@ const productItemImages = document.querySelectorAll('.product__with-img-swap') productItemImages.forEach(productItemImage => { productItemImage.addEventListener('mouseover', event => { - [event.target.src, event.target.dataset.altimgSrc] = valueSwap(event.target.src, event.target.dataset.altimgSrc) + if (event.target.dataset.altimgSrc != '') { + [event.target.src, event.target.dataset.altimgSrc] = valueSwap(event.target.src, event.target.dataset.altimgSrc) + } }) productItemImage.addEventListener('mouseout', event => { - [event.target.src, event.target.dataset.altimgSrc] = valueSwap(event.target.src, event.target.dataset.altimgSrc) + if (event.target.dataset.altimgSrc != '') { + [event.target.src, event.target.dataset.altimgSrc] = valueSwap(event.target.src, event.target.dataset.altimgSrc) + } }) }) diff --git a/src/storefront/cart.py b/src/storefront/cart.py index ff0c03e..38e9fe1 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -6,10 +6,12 @@ from django.conf import settings from django.contrib import messages from django.shortcuts import redirect, reverse from django.urls import reverse_lazy +from django.core.cache import cache from django.db.models import OuterRef, Q, Subquery from core.models import ( - Product, ProductVariant, OrderLine, Coupon, ShippingRate + Product, ProductVariant, OrderLine, Coupon, ShippingRate, + SiteSettings ) from core.usps import USPSApi from core import ( @@ -58,6 +60,7 @@ class CartItem: class Cart: item_class = CartItem + site_settings = SiteSettings.load() def __init__(self, request): self.request = request @@ -101,8 +104,15 @@ class Cart: for item in self: if item['variant'].track_inventory: if item['quantity'] > item['variant'].stock: - messages.warning(request, 'Quantity added exceeds available stock.') + if item['quantity'] > item['variant'].product.checkout_limit: + messages.warning(request, 'Quantity exceeds checkout limit.') + item['quantity'] = item['variant'].product.checkout_limit + continue + messages.warning(request, 'Quantity exceeds available stock.') item['quantity'] = item['variant'].stock + elif item['quantity'] > item['variant'].product.checkout_limit: + messages.warning(request, 'Quantity exceeds checkout limit.') + item['quantity'] = item['variant'].product.checkout_limit self.save() def remove(self, pk): @@ -160,6 +170,10 @@ class Cart: return containers def get_shipping_cost(self, container=None): + free_shipping_min = self.site_settings.free_shipping_min + if self.get_total_price() >= free_shipping_min: + return Decimal('0.00') + if container is None: container = self.session.get('shipping_container').container diff --git a/src/storefront/templates/storefront/order_detail.html b/src/storefront/templates/storefront/order_detail.html index 3ee8ba9..edfe4b0 100644 --- a/src/storefront/templates/storefront/order_detail.html +++ b/src/storefront/templates/storefront/order_detail.html @@ -30,7 +30,7 @@ {{item.customer_note}} {{item.quantity}} - ${{product.price}} + ${{item.unit_price}} ${{item.get_total}} {% endwith %} diff --git a/src/storefront/templates/storefront/order_form.html b/src/storefront/templates/storefront/order_form.html index 8614c54..4e782d3 100644 --- a/src/storefront/templates/storefront/order_form.html +++ b/src/storefront/templates/storefront/order_form.html @@ -75,7 +75,11 @@ {% endif %} Shipping - ${{ cart.get_shipping_cost }} + {% if cart.get_total_price >= site_settings.free_shipping_min %} + Free! + {% else %} + ${{ cart.get_shipping_cost }} + {% endif %} Total diff --git a/src/storefront/views.py b/src/storefront/views.py index 7e46c41..60f951a 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -178,6 +178,11 @@ class ProductCategoryDetailView(DetailView): Q(visible_in_listings=True), Q(variants__track_inventory=False) | Q(variants__track_inventory=True) & Q(variants__stock__gt=0) + ).prefetch_related( + Prefetch( + 'variants', + queryset=ProductVariant.objects.all().order_by('sorting', 'weight') + ) ).distinct() ) ) @@ -192,6 +197,11 @@ class ProductListView(ListView): queryset = Product.objects.filter( visible_in_listings=True, category__main_category=True + ).prefetch_related( + Prefetch( + 'variants', + queryset=ProductVariant.objects.all().order_by('sorting', 'weight') + ) ) @@ -206,7 +216,7 @@ class ProductDetailView(FormMixin, DetailView): track_inventory=True, stock__gt=0 ) - ) + ).order_by('name') options = ProductOption.objects.filter(products__pk=self.object.pk) if form_class is None: form_class = self.get_form_class() From 0dd19b2d5b3a2cd571e04dc86c5b873540d44311 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 5 Nov 2022 11:27:48 -0600 Subject: [PATCH 07/11] Add cart stock limits --- src/core/models.py | 26 ++++++++++++++++++- src/dashboard/templates/dashboard/config.html | 12 +++++++++ .../templates/dashboard/order_detail.html | 2 +- src/dashboard/urls.py | 5 ++++ .../templates/storefront/cart_detail.html | 1 + 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/core/models.py b/src/core/models.py index a9a32d4..54eaebd 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -71,6 +71,14 @@ class ProductCategory(models.Model): verbose_name_plural = 'Product Categories' +class ProductManager(models.Manager): + def get_queryset(self): + return super().get_queryset().prefetch_related( + 'productphoto_set', + 'options' + ) + + class Product(models.Model): category = models.ForeignKey( ProductCategory, @@ -92,6 +100,8 @@ class Product(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + objects = ProductManager() + def __str__(self): return self.name @@ -124,6 +134,12 @@ class ProductVariant(models.Model): on_delete=models.CASCADE, related_name='variants' ) + image = models.ForeignKey( + 'ProductPhoto', + on_delete=models.SET_NULL, + related_name='+', + null=True + ) name = models.CharField(max_length=255) sku = models.CharField(max_length=255, unique=True) stripe_id = models.CharField(max_length=255, blank=True) @@ -145,6 +161,7 @@ class ProductVariant(models.Model): null=True, validators=[MinValueValidator(0)] ) + sorting = models.PositiveIntegerField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -155,7 +172,7 @@ class ProductVariant(models.Model): return f'{self.product}: {self.name}' class Meta: - ordering = ['weight'] + ordering = ['sorting', 'weight'] class ProductOption(models.Model): @@ -491,6 +508,13 @@ class SiteSettings(SingletonBase): related_name='+', on_delete=models.SET_NULL ) + free_shipping_min = models.DecimalField( + max_digits=settings.DEFAULT_MAX_DIGITS, + decimal_places=settings.DEFAULT_DECIMAL_PLACES, + blank=True, + null=True, + help_text='Minimum dollar amount in the cart subtotal to qualify for free shipping' + ) def __str__(self): return 'Site Settings' diff --git a/src/dashboard/templates/dashboard/config.html b/src/dashboard/templates/dashboard/config.html index 5e56eb0..70bf292 100644 --- a/src/dashboard/templates/dashboard/config.html +++ b/src/dashboard/templates/dashboard/config.html @@ -23,5 +23,17 @@ {% endfor %} + +
+
+

Site Settings

+ Edit +
+
+

USPS User ID: {{ site_settings.usps_user_id }}

+

Default shipping rate: {{ site_settings.default_shipping_rate }}

+

Free shipping min: ${{ site_settings.free_shipping_min }}

+
+
{% endblock %} diff --git a/src/dashboard/templates/dashboard/order_detail.html b/src/dashboard/templates/dashboard/order_detail.html index c530b70..6cd9a93 100644 --- a/src/dashboard/templates/dashboard/order_detail.html +++ b/src/dashboard/templates/dashboard/order_detail.html @@ -30,7 +30,7 @@ {{product.sku}} {{item.quantity}} - ${{item.variant.price}} + ${{item.unit_price}} ${{item.get_total}} {% endwith %} diff --git a/src/dashboard/urls.py b/src/dashboard/urls.py index dc2d70c..2b0b180 100644 --- a/src/dashboard/urls.py +++ b/src/dashboard/urls.py @@ -12,6 +12,11 @@ urlpatterns = [ views.DashboardConfigView.as_view(), name='config' ), + path( + 'settings//update/', + views.SiteSettingsUpdateView.as_view(), + name='settings-update' + ), path( 'catalog/', views.CatalogView.as_view(), diff --git a/src/storefront/templates/storefront/cart_detail.html b/src/storefront/templates/storefront/cart_detail.html index 9ff2cc8..f5e0451 100644 --- a/src/storefront/templates/storefront/cart_detail.html +++ b/src/storefront/templates/storefront/cart_detail.html @@ -54,6 +54,7 @@

+
Free shipping on orders over ${{ site_settings.free_shipping_min|floatformat:"2" }}
From 4b8831035e34558c82e346a645a5dc4ac8a16322 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 5 Nov 2022 17:46:22 -0600 Subject: [PATCH 08/11] Add correct image for selected variant --- src/dashboard/views.py | 10 ++-------- src/storefront/templates/storefront/cart_detail.html | 4 ++++ src/storefront/templates/storefront/order_detail.html | 4 ++++ src/storefront/templates/storefront/order_form.html | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 7f145c6..ae5d8e5 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -47,6 +47,7 @@ from core import ( OrderStatus ) from .forms import ( + ProductVariantUpdateForm, OrderLineFulfillForm, OrderLineFormset, OrderCancelForm, @@ -438,14 +439,7 @@ class ProductVariantUpdateView(SuccessMessageMixin, UpdateView): pk_url_kwarg = 'variant_pk' success_message = 'ProductVariant saved.' template_name = 'dashboard/variant_form.html' - fields = [ - 'name', - 'sku', - 'price', - 'weight', - 'track_inventory', - 'stock', - ] + form_class = ProductVariantUpdateForm context_object_name = 'variant' def get_context_data(self, **kwargs): diff --git a/src/storefront/templates/storefront/cart_detail.html b/src/storefront/templates/storefront/cart_detail.html index f5e0451..75679a1 100644 --- a/src/storefront/templates/storefront/cart_detail.html +++ b/src/storefront/templates/storefront/cart_detail.html @@ -13,7 +13,11 @@
{% with product=item.variant.product %}
+ {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} {{product.get_first_img.image}} + {% endif %}

{{product.name}}

diff --git a/src/storefront/templates/storefront/order_detail.html b/src/storefront/templates/storefront/order_detail.html index edfe4b0..cdfe16d 100644 --- a/src/storefront/templates/storefront/order_detail.html +++ b/src/storefront/templates/storefront/order_detail.html @@ -23,7 +23,11 @@
{% with product=item.variant.product %}
+ {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} {{product.get_first_img.image}} + {% endif %} {{ item.variant }}
diff --git a/src/storefront/templates/storefront/order_form.html b/src/storefront/templates/storefront/order_form.html index 4e782d3..2e24fdb 100644 --- a/src/storefront/templates/storefront/order_form.html +++ b/src/storefront/templates/storefront/order_form.html @@ -34,7 +34,11 @@
{% with product=item.variant.product %}
+ {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} {{product.get_first_img.image}} + {% endif %}

{{product.name}}

From 0e52e1bdfe8bfae729074df23ddd7f20f4d586f9 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 5 Nov 2022 20:35:16 -0600 Subject: [PATCH 09/11] Add variant photo assoc --- src/core/fixtures/new_coffee_variants.json | 18 +++++++ src/core/models.py | 48 +++++++++---------- src/dashboard/forms.py | 22 +++++++++ .../templates/dashboard/order_detail.html | 6 ++- src/dashboard/templates/dashboard/stock.html | 4 ++ src/dashboard/views.py | 1 + 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/core/fixtures/new_coffee_variants.json b/src/core/fixtures/new_coffee_variants.json index af5ad89..1a38570 100644 --- a/src/core/fixtures/new_coffee_variants.json +++ b/src/core/fixtures/new_coffee_variants.json @@ -10,6 +10,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -25,6 +26,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -40,6 +42,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -55,6 +58,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -70,6 +74,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -85,6 +90,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -100,6 +106,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -115,6 +122,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -130,6 +138,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -145,6 +154,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -160,6 +170,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -175,6 +186,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -190,6 +202,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -205,6 +218,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -220,6 +234,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -235,6 +250,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -250,6 +266,7 @@ "track_inventory": false, "stock": null, "sorting": 1, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } @@ -265,6 +282,7 @@ "track_inventory": false, "stock": null, "sorting": 3, + "image": null, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-02-23T18:06:57.624Z" } diff --git a/src/core/models.py b/src/core/models.py index 54eaebd..d53decf 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -121,6 +121,29 @@ class Product(models.Model): ordering = ['sorting', 'name'] +class ProductPhoto(models.Model): + product = models.ForeignKey(Product, on_delete=models.CASCADE) + image = models.ImageField(upload_to='products/images') + + def __str__(self): + return f'{self.product.name} {self.image}' + + def delete(self, *args, **kwargs): + storage, path = self.image.storage, self.image.path + + super(ProductPhoto, self).delete(*args, **kwargs) + storage.delete(path) + + # def save(self, *args, **kwargs): + # super().save(*args, **kwargs) + + # img = Image.open(self.image.path) + # if img.height > 400 or img.width > 400: + # output_size = (400, 400) + # img.thumbnail(output_size) + # img.save(self.image.path) + + class ProductVariantManager(models.Manager): def get_queryset(self): return super().get_queryset().annotate( @@ -135,7 +158,7 @@ class ProductVariant(models.Model): related_name='variants' ) image = models.ForeignKey( - 'ProductPhoto', + ProductPhoto, on_delete=models.SET_NULL, related_name='+', null=True @@ -195,29 +218,6 @@ class ProductOption(models.Model): return f'{self.name}' -class ProductPhoto(models.Model): - product = models.ForeignKey(Product, on_delete=models.CASCADE) - image = models.ImageField(upload_to='products/images') - - def __str__(self): - return self.product.name - - def delete(self, *args, **kwargs): - storage, path = self.image.storage, self.image.path - - super(ProductPhoto, self).delete(*args, **kwargs) - storage.delete(path) - - # def save(self, *args, **kwargs): - # super().save(*args, **kwargs) - - # img = Image.open(self.image.path) - # if img.height > 400 or img.width > 400: - # output_size = (400, 400) - # img.thumbnail(output_size) - # img.save(self.image.path) - - class Coupon(models.Model): type = models.CharField( max_length=20, diff --git a/src/dashboard/forms.py b/src/dashboard/forms.py index 3d75eff..b43e9d0 100644 --- a/src/dashboard/forms.py +++ b/src/dashboard/forms.py @@ -3,6 +3,7 @@ from django import forms from core import OrderStatus from core.models import ( + ProductVariant, Order, OrderLine, ShippingRate, @@ -14,6 +15,27 @@ from core.models import ( logger = logging.getLogger(__name__) +class ProductVariantUpdateForm(forms.ModelForm): + class Meta: + model = ProductVariant + fields = [ + 'name', + 'sku', + 'price', + 'weight', + 'track_inventory', + 'stock', + 'image' + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance: + self.fields['image'].queryset = ProductPhoto.objects.filter( + product=self.instance.product + ) + + class CouponForm(forms.ModelForm): class Meta: model = Coupon diff --git a/src/dashboard/templates/dashboard/order_detail.html b/src/dashboard/templates/dashboard/order_detail.html index 6cd9a93..3326809 100644 --- a/src/dashboard/templates/dashboard/order_detail.html +++ b/src/dashboard/templates/dashboard/order_detail.html @@ -25,7 +25,11 @@
{% with product=item.variant.product %}
- {{product.get_first_img.image}} + {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} + {{product.get_first_img.image}} + {% endif %}
{{item.variant}}
{{item.customer_note}}
{{product.sku}} diff --git a/src/dashboard/templates/dashboard/stock.html b/src/dashboard/templates/dashboard/stock.html index b846316..9c177b2 100644 --- a/src/dashboard/templates/dashboard/stock.html +++ b/src/dashboard/templates/dashboard/stock.html @@ -19,7 +19,11 @@
{% with product=variant.product %}
+ {% if variant.image %} + {{variant.image.image}} + {% else %} {{product.get_first_img.image}} + {% endif %}
{{variant}}
{{ variant.sku }} diff --git a/src/dashboard/views.py b/src/dashboard/views.py index ae5d8e5..48146ed 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -2,6 +2,7 @@ import logging from datetime import datetime from django.conf import settings from django.utils import timezone +from django import forms from django.shortcuts import render, reverse, redirect, get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse, reverse_lazy From 087728b827974d48b32b985153d03ab4c041a4dd Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sun, 6 Nov 2022 10:00:40 -0700 Subject: [PATCH 10/11] Update banner styles --- src/static/styles/main.css | 7 ++++++- src/storefront/templates/storefront/product_list.html | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/static/styles/main.css b/src/static/styles/main.css index 937ad1d..8c81665 100644 --- a/src/static/styles/main.css +++ b/src/static/styles/main.css @@ -441,13 +441,14 @@ section:not(:last-child) { /* Site Banner ========================================================================== */ .site__banner { - background-color: rgba(0, 0, 0, 0.44); + background-color: rgba(0, 0, 0, 0.6); background-blend-mode: multiply; background-size: cover; background-position: center; color: #f1e8e2; text-align: center; padding: 2rem 1rem; + text-shadow: 1px 1px 2px black; font-family: 'Vollkorn', serif; } @@ -792,6 +793,10 @@ article + article { text-align: right; } +.item__variant { + color: var(--red-color); +} + .item__form, .coupon__form p { display: flex; diff --git a/src/storefront/templates/storefront/product_list.html b/src/storefront/templates/storefront/product_list.html index ae9b85f..70535b9 100644 --- a/src/storefront/templates/storefront/product_list.html +++ b/src/storefront/templates/storefront/product_list.html @@ -15,8 +15,7 @@ {% block content %}
-

Welcome to our new website!

-

NEW COOL LOOK, SAME GREAT COFFEE

+

Now three different size bags to choose from!

From 388556723ea2ed1a6cf45a91a98ef23e0e7c7923 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sun, 6 Nov 2022 10:00:57 -0700 Subject: [PATCH 11/11] Suspend free shipping for now --- src/storefront/cart.py | 8 ++++---- src/storefront/templates/storefront/cart_detail.html | 4 ++-- src/storefront/templates/storefront/order_form.html | 4 ---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/storefront/cart.py b/src/storefront/cart.py index 38e9fe1..0898156 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -60,7 +60,7 @@ class CartItem: class Cart: item_class = CartItem - site_settings = SiteSettings.load() + # site_settings = SiteSettings.load() def __init__(self, request): self.request = request @@ -170,9 +170,9 @@ class Cart: return containers def get_shipping_cost(self, container=None): - free_shipping_min = self.site_settings.free_shipping_min - if self.get_total_price() >= free_shipping_min: - return Decimal('0.00') + # free_shipping_min = self.site_settings.free_shipping_min + # if self.get_total_price() >= free_shipping_min: + # return Decimal('0.00') if container is None: container = self.session.get('shipping_container').container diff --git a/src/storefront/templates/storefront/cart_detail.html b/src/storefront/templates/storefront/cart_detail.html index 75679a1..6b34eb6 100644 --- a/src/storefront/templates/storefront/cart_detail.html +++ b/src/storefront/templates/storefront/cart_detail.html @@ -21,7 +21,7 @@

{{product.name}}

-

{{ item.variant.name }}

+

{{ item.variant.name }}

{% for key, value in item.options.items %}

{{ key }}: {{ value }}

{% endfor %} @@ -58,7 +58,7 @@

-
Free shipping on orders over ${{ site_settings.free_shipping_min|floatformat:"2" }}
+
diff --git a/src/storefront/templates/storefront/order_form.html b/src/storefront/templates/storefront/order_form.html index 2e24fdb..03d8c0e 100644 --- a/src/storefront/templates/storefront/order_form.html +++ b/src/storefront/templates/storefront/order_form.html @@ -79,11 +79,7 @@ {% endif %} - {% if cart.get_total_price >= site_settings.free_shipping_min %} - - {% else %} - {% endif %}
ShippingFree!${{ cart.get_shipping_cost }}
Total