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 %}
+
+ USPS User ID: {{ site_settings.usps_user_id }} Default shipping rate: {{ site_settings.default_shipping_rate }} Free shipping min: ${{ site_settings.free_shipping_min }}Site Settings
+ Edit
+ Update Site Settings
+
| {{item.quantity}} | -${{product.price}} | +${{item.unit_price}} | ${{item.get_total}} | {% endwith %}
| 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()
|---|