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/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/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/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/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/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" }}
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}} - + {% 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 %} - + {% if cart.get_total_price >= site_settings.free_shipping_min %} + + {% else %} + + {% endif %} 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()
{{item.quantity}}${{product.price}}${{item.unit_price}} ${{item.get_total}}
Shipping${{ cart.get_shipping_cost }}Free!${{ cart.get_shipping_cost }}
Total