diff --git a/src/accounts/models.py b/src/accounts/models.py
index 70b657d..1647c59 100644
--- a/src/accounts/models.py
+++ b/src/accounts/models.py
@@ -18,7 +18,12 @@ class Address(models.Model):
postal_code = models.CharField(max_length=20, blank=True)
def __str__(self):
- return f'{self.street_address_1} — {self.city}'
+ return f"""
+ {first_name} {last_name}
+ {street_address_1}
+ {street_address_2}
+ {city}, {state}, {postal_code}
+ """
class User(AbstractUser):
diff --git a/src/accounts/utils.py b/src/accounts/utils.py
index 11d4cf4..f1b24a9 100644
--- a/src/accounts/utils.py
+++ b/src/accounts/utils.py
@@ -23,7 +23,7 @@ def get_or_create_customer(request, form, shipping_address):
user.save()
else:
user, u_created = User.objects.get_or_create(
- email=form.cleaned_data['email'],
+ email=form.cleaned_data['email'].lower(),
defaults={
'username': form.cleaned_data['email'].lower(),
'is_staff': False,
diff --git a/src/core/__init__.py b/src/core/__init__.py
index 16798fe..f8e65f7 100644
--- a/src/core/__init__.py
+++ b/src/core/__init__.py
@@ -66,13 +66,15 @@ class TransactionStatus:
]
-class ShippingMethodType:
- PRICE_BASED = 'price'
- WEIGHT_BASED = 'weight'
+class ShippingProvider:
+ USPS = 'USPS'
+ # UPS = 'UPS'
+ # FEDEX = 'FEDEX'
CHOICES = [
- (PRICE_BASED, 'Price based shipping'),
- (WEIGHT_BASED, 'Weight based shipping'),
+ (USPS, 'USPS'),
+ # (UPS, 'UPS'),
+ # (FEDEX, 'FedEx'),
]
@@ -125,3 +127,20 @@ class CoffeeGrind:
(PERCOLATOR, 'Percolator'),
(CAFE_STYLE, 'BLTC cafe pour over')
]
+
+
+def build_usps_rate_request(weight, container, zip_destination):
+ return \
+ {
+ 'service': ShippingService.PRIORITY_COMMERCIAL,
+ 'zip_origination': settings.DEFAULT_ZIP_ORIGINATION,
+ 'zip_destination': zip_destination,
+ 'pounds': '0',
+ 'ounces': weight,
+ 'container': container,
+ 'width': '',
+ 'length': '',
+ 'height': '',
+ 'girth': '',
+ 'machinable': 'TRUE'
+ }
diff --git a/src/core/admin.py b/src/core/admin.py
index 62c9ccc..324c630 100644
--- a/src/core/admin.py
+++ b/src/core/admin.py
@@ -1,19 +1,27 @@
from django.contrib import admin
from .models import (
+ SiteSettings,
+ ProductCategory,
Product,
ProductPhoto,
+ ProductVariant,
+ ProductOption,
Coupon,
- ShippingMethod,
+ ShippingRate,
Order,
Transaction,
OrderLine,
)
+admin.site.register(SiteSettings)
+admin.site.register(ProductCategory)
admin.site.register(Product)
admin.site.register(ProductPhoto)
+admin.site.register(ProductVariant)
+admin.site.register(ProductOption)
admin.site.register(Coupon)
-admin.site.register(ShippingMethod)
+admin.site.register(ShippingRate)
admin.site.register(Order)
admin.site.register(Transaction)
admin.site.register(OrderLine)
diff --git a/src/core/context_processors.py b/src/core/context_processors.py
new file mode 100644
index 0000000..eb6f1c8
--- /dev/null
+++ b/src/core/context_processors.py
@@ -0,0 +1,5 @@
+from .models import SiteSettings
+
+
+def site_settings(request):
+ return {'site_settings': SiteSettings.load()}
diff --git a/src/core/fixtures/orders.json b/src/core/fixtures/orders.json
index f0c17c2..5b40378 100644
--- a/src/core/fixtures/orders.json
+++ b/src/core/fixtures/orders.json
@@ -10,7 +10,7 @@
"shipping_method": null,
"coupon": null,
"shipping_total": "9.55",
- "total_net_amount": "13.40",
+ "subtotal_amount": "13.40",
"weight": "0.0:oz",
"created_at": "2022-03-15T17:18:59.584Z",
"updated_at": "2022-03-15T17:18:59.584Z"
@@ -26,7 +26,7 @@
"shipping_method": null,
"coupon": null,
"shipping_total": "9.55",
- "total_net_amount": "13.40",
+ "subtotal_amount": "13.40",
"weight": "0.0:oz",
"created_at": "2022-03-15T17:22:18.440Z",
"updated_at": "2022-03-15T17:22:18.440Z"
@@ -42,7 +42,7 @@
"shipping_method": null,
"coupon": null,
"shipping_total": "9.55",
- "total_net_amount": "13.40",
+ "subtotal_amount": "13.40",
"weight": "0.0:oz",
"created_at": "2022-03-15T17:26:27.869Z",
"updated_at": "2022-03-15T17:26:27.869Z"
diff --git a/src/core/forms.py b/src/core/forms.py
index 50990a6..46b1d1f 100644
--- a/src/core/forms.py
+++ b/src/core/forms.py
@@ -2,11 +2,11 @@ import logging
from django import forms
from django.core.mail import EmailMessage
-from core.models import Order, OrderLine, ShippingMethod
+from core.models import Order, OrderLine, ShippingRate
logger = logging.getLogger(__name__)
-class ShippingMethodForm(forms.ModelForm):
+class ShippingRateForm(forms.ModelForm):
class Meta:
- model = ShippingMethod
+ model = ShippingRate
fields = '__all__'
diff --git a/src/core/migrations/0011_productcategory_productoption_productvariant_and_more.py b/src/core/migrations/0011_productcategory_productoption_productvariant_and_more.py
new file mode 100644
index 0000000..8dce6ee
--- /dev/null
+++ b/src/core/migrations/0011_productcategory_productoption_productvariant_and_more.py
@@ -0,0 +1,130 @@
+# Generated by Django 4.0.2 on 2022-09-07 20:34
+
+from django.conf import settings
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django_measurement.models
+import measurement.measures.mass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('core', '0010_product_stripe_id'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ProductCategory',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ],
+ options={
+ 'verbose_name': 'Product Category',
+ 'verbose_name_plural': 'Product Categories',
+ },
+ ),
+ migrations.CreateModel(
+ name='ProductOption',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('options', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), size=None)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ProductVariant',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('sku', models.CharField(max_length=255, unique=True)),
+ ('stripe_id', models.CharField(blank=True, max_length=255)),
+ ('price', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)),
+ ('weight', django_measurement.models.MeasurementField(blank=True, measurement=measurement.measures.mass.Mass, null=True)),
+ ('track_inventory', models.BooleanField(default=False)),
+ ('stock', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)])),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ShippingRate',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('shipping_provider', models.CharField(choices=[('USPS', 'USPS')], default='USPS', max_length=255)),
+ ('name', models.CharField(max_length=255)),
+ ('container', models.CharField(choices=[('LG FLAT RATE BOX', 'Flate Rate Box - Large'), ('MD FLAT RATE BOX', 'Flate Rate Box - Medium'), ('REGIONALRATEBOXA', 'Regional Rate Box A'), ('REGIONALRATEBOXB', 'Regional Rate Box B'), ('VARIABLE', 'Variable')], default='VARIABLE', max_length=255)),
+ ('min_order_weight', models.PositiveIntegerField(blank=True, null=True)),
+ ('max_order_weight', models.PositiveIntegerField(blank=True, null=True)),
+ ],
+ options={
+ 'ordering': ['min_order_weight'],
+ },
+ ),
+ migrations.CreateModel(
+ name='SiteSettings',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('usps_user_id', models.CharField(max_length=255)),
+ ],
+ options={
+ 'verbose_name': 'Site Settings',
+ 'verbose_name_plural': 'Site Settings',
+ },
+ ),
+ migrations.CreateModel(
+ name='Subscription',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('stripe_id', models.CharField(blank=True, max_length=255)),
+ ('customer', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscription', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.RemoveField(
+ model_name='order',
+ name='shipping_method',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='price',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='sku',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='stripe_id',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='weight',
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='checkout_limit',
+ field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)]),
+ ),
+ migrations.DeleteModel(
+ name='ShippingMethod',
+ ),
+ migrations.AddField(
+ model_name='productvariant',
+ name='product',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product'),
+ ),
+ migrations.AddField(
+ model_name='productoption',
+ name='product',
+ field=models.ManyToManyField(related_name='options', to='core.Product'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='category',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.productcategory'),
+ ),
+ ]
diff --git a/src/core/migrations/0012_alter_productvariant_product.py b/src/core/migrations/0012_alter_productvariant_product.py
new file mode 100644
index 0000000..52796fb
--- /dev/null
+++ b/src/core/migrations/0012_alter_productvariant_product.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.0.2 on 2022-09-07 21:24
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0011_productcategory_productoption_productvariant_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='productvariant',
+ name='product',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='core.product'),
+ ),
+ ]
diff --git a/src/core/migrations/0013_rename_total_net_amount_order_subtotal_amount_and_more.py b/src/core/migrations/0013_rename_total_net_amount_order_subtotal_amount_and_more.py
new file mode 100644
index 0000000..975ad85
--- /dev/null
+++ b/src/core/migrations/0013_rename_total_net_amount_order_subtotal_amount_and_more.py
@@ -0,0 +1,38 @@
+# Generated by Django 4.0.2 on 2022-10-01 18:45
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0012_alter_productvariant_product'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='order',
+ old_name='total_net_amount',
+ new_name='subtotal_amount',
+ ),
+ migrations.RemoveField(
+ model_name='orderline',
+ name='product',
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='coupon_amount',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='total_amount',
+ field=models.DecimalField(decimal_places=2, default=0, max_digits=10),
+ ),
+ migrations.AddField(
+ model_name='orderline',
+ name='variant',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_lines', to='core.productvariant'),
+ ),
+ ]
diff --git a/src/core/migrations/0014_alter_productvariant_options_and_more.py b/src/core/migrations/0014_alter_productvariant_options_and_more.py
new file mode 100644
index 0000000..44602cf
--- /dev/null
+++ b/src/core/migrations/0014_alter_productvariant_options_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.0.2 on 2022-10-13 01:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0013_rename_total_net_amount_order_subtotal_amount_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='productvariant',
+ options={'ordering': ['weight']},
+ ),
+ migrations.RenameField(
+ model_name='productoption',
+ old_name='product',
+ new_name='products',
+ ),
+ ]
diff --git a/src/core/migrations/0015_productcategory_main_product.py b/src/core/migrations/0015_productcategory_main_product.py
new file mode 100644
index 0000000..ba0d3e2
--- /dev/null
+++ b/src/core/migrations/0015_productcategory_main_product.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0.2 on 2022-10-15 22:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0014_alter_productvariant_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='productcategory',
+ name='main_product',
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/src/core/migrations/0016_rename_main_product_productcategory_main_category.py b/src/core/migrations/0016_rename_main_product_productcategory_main_category.py
new file mode 100644
index 0000000..3a6c009
--- /dev/null
+++ b/src/core/migrations/0016_rename_main_product_productcategory_main_category.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0.2 on 2022-10-15 22:15
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0015_productcategory_main_product'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='productcategory',
+ old_name='main_product',
+ new_name='main_category',
+ ),
+ ]
diff --git a/src/core/models.py b/src/core/models.py
index 2e01a09..e5c8bc9 100644
--- a/src/core/models.py
+++ b/src/core/models.py
@@ -8,8 +8,10 @@ from django.db.models.functions import Coalesce
from django.conf import settings
from django.utils import timezone
from django.urls import reverse
+from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.serializers.json import DjangoJSONEncoder
+from django.contrib.postgres.fields import ArrayField, HStoreField
from django.forms.models import model_to_dict
from django_measurement.models import MeasurementField
@@ -21,44 +23,78 @@ from . import (
VoucherType,
TransactionStatus,
OrderStatus,
- ShippingMethodType
+ ShippingProvider,
+ ShippingContainer,
+ build_usps_rate_request
)
from .weight import WeightUnits, zero_weight
logger = logging.getLogger(__name__)
-class ProductEncoder(DjangoJSONEncoder):
- def default(self, obj):
- logger.info(f"\n{obj}\n")
- return super().default(obj)
+class SingletonBase(models.Model):
+ def set_cache(self):
+ cache.set(self.__class__.__name__, self)
+
+ def save(self, *args, **kwargs):
+ self.pk = 1
+ super(SingletonBase, self).save(*args, **kwargs)
+ self.set_cache()
+
+ def delete(self, *args, **kwargs):
+ pass
+
+ @classmethod
+ def load(cls):
+ if cache.get(cls.__name__) is None:
+ obj, created = cls.objects.get_or_create(pk=1)
+ if not created:
+ obj.set_cache()
+ return cache.get(cls.__name__)
+
+ class Meta:
+ abstract = True
-class ProductManager(models.Manager):
- def get_queryset(self):
- return super().get_queryset().annotate(
- num_ordered=models.Sum('order_lines__quantity')
- )
+class SiteSettings(SingletonBase):
+ usps_user_id = models.CharField(max_length=255)
+
+ def __str__(self):
+ return 'Site Settings'
+
+ class Meta:
+ verbose_name = 'Site Settings'
+ verbose_name_plural = 'Site Settings'
+
+
+class ProductCategory(models.Model):
+ name = models.CharField(max_length=255)
+ main_category = models.BooleanField(default=True)
+
+ def __str__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse('dashboard:category-detail', kwargs={'pk': self.pk})
+
+ class Meta:
+ verbose_name = 'Product Category'
+ verbose_name_plural = 'Product Categories'
class Product(models.Model):
+ category = models.ForeignKey(
+ ProductCategory,
+ blank=True,
+ null=True,
+ on_delete=models.SET_NULL
+ )
name = models.CharField(max_length=250)
subtitle = models.CharField(max_length=250, blank=True)
description = models.TextField(blank=True)
- sku = models.CharField(max_length=255, unique=True)
- stripe_id = models.CharField(max_length=255, blank=True)
- price = models.DecimalField(
- max_digits=settings.DEFAULT_MAX_DIGITS,
- decimal_places=settings.DEFAULT_DECIMAL_PLACES,
- blank=True,
- null=True,
- )
- stripe_price_id = models.CharField(max_length=255, blank=True)
- weight = MeasurementField(
- measurement=Weight,
- unit_choices=WeightUnits.CHOICES,
- blank=True,
- null=True
+ checkout_limit = models.IntegerField(
+ default=0,
+ validators=[MinValueValidator(0)]
)
visible_in_listings = models.BooleanField(default=False)
@@ -67,8 +103,6 @@ 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
@@ -88,6 +122,73 @@ class Product(models.Model):
ordering = ['sorting', 'name']
+class ProductVariantManager(models.Manager):
+ def get_queryset(self):
+ return super().get_queryset().annotate(
+ num_ordered=models.Sum('order_lines__quantity')
+ )
+
+
+class ProductVariant(models.Model):
+ product = models.ForeignKey(
+ Product,
+ on_delete=models.CASCADE,
+ related_name='variants'
+ )
+ name = models.CharField(max_length=255)
+ sku = models.CharField(max_length=255, unique=True)
+ stripe_id = models.CharField(max_length=255, blank=True)
+ price = models.DecimalField(
+ max_digits=settings.DEFAULT_MAX_DIGITS,
+ decimal_places=settings.DEFAULT_DECIMAL_PLACES,
+ blank=True,
+ null=True,
+ )
+ weight = MeasurementField(
+ measurement=Weight,
+ unit_choices=WeightUnits.CHOICES,
+ blank=True,
+ null=True
+ )
+ track_inventory = models.BooleanField(default=False)
+ stock = models.IntegerField(
+ blank=True,
+ null=True,
+ validators=[MinValueValidator(0)]
+ )
+
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ objects = ProductVariantManager()
+
+ def __str__(self):
+ return f'{self.product}: {self.name}'
+
+ class Meta:
+ ordering = ['weight']
+
+
+class ProductOption(models.Model):
+ """
+ Description: Consistent accross all variants
+ """
+ products = models.ManyToManyField(
+ Product,
+ related_name='options'
+ )
+ name = models.CharField(max_length=255)
+ options = ArrayField(
+ models.CharField(max_length=255)
+ )
+
+ def get_absolute_url(self):
+ return reverse('dashboard:option-detail', kwargs={'pk': self.pk})
+
+ def __str__(self):
+ return f'{self.name}'
+
+
class ProductPhoto(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
image = models.ImageField(upload_to='products/images')
@@ -150,17 +251,32 @@ class Coupon(models.Model):
return reverse('dashboard:coupon-detail', kwargs={'pk': self.pk})
-class ShippingMethod(models.Model):
- name = models.CharField(max_length=100)
- type = models.CharField(max_length=30, choices=ShippingMethodType.CHOICES)
- price = models.DecimalField(
- max_digits=settings.DEFAULT_MAX_DIGITS,
- decimal_places=settings.DEFAULT_DECIMAL_PLACES,
- default=0,
+class ShippingRate(models.Model):
+ shipping_provider = models.CharField(
+ max_length=255,
+ choices=ShippingProvider.CHOICES,
+ default=ShippingProvider.USPS
+ )
+ name = models.CharField(max_length=255)
+ container = models.CharField(
+ max_length=255,
+ choices=ShippingContainer.CHOICES,
+ default=ShippingContainer.VARIABLE
+ )
+ min_order_weight = models.PositiveIntegerField(
+ blank=True,
+ null=True
+ )
+ max_order_weight = models.PositiveIntegerField(
+ blank=True,
+ null=True
)
- def get_absolute_url(self):
- return reverse('dashboard:shipmeth-detail', kwargs={'pk': self.pk})
+ def __str__(self):
+ return f'{self.shipping_provider}: {self.name} ({self.min_order_weight}–{self.max_order_weight})'
+
+ class Meta:
+ ordering = ['min_order_weight']
class OrderManager(models.Manager):
@@ -188,7 +304,7 @@ class OrderManager(models.Manager):
class Order(models.Model):
customer = models.ForeignKey(
User,
- related_name="orders",
+ related_name='orders',
on_delete=models.SET_NULL,
null=True
)
@@ -211,14 +327,6 @@ class Order(models.Model):
null=True,
on_delete=models.SET_NULL
)
- shipping_method = models.ForeignKey(
- ShippingMethod,
- blank=True,
- null=True,
- related_name="orders",
- on_delete=models.SET_NULL
- )
-
coupon = models.ForeignKey(
Coupon,
related_name='orders',
@@ -226,19 +334,22 @@ class Order(models.Model):
blank=True,
null=True
)
-
+ subtotal_amount = models.DecimalField(
+ max_digits=10,
+ decimal_places=2,
+ default=0
+ )
+ coupon_amount = models.CharField(max_length=255, blank=True)
shipping_total = models.DecimalField(
max_digits=5,
decimal_places=2,
default=0
)
-
- total_net_amount = models.DecimalField(
+ total_amount = models.DecimalField(
max_digits=10,
decimal_places=2,
default=0
)
-
weight = MeasurementField(
measurement=Weight,
unit_choices=WeightUnits.CHOICES,
@@ -260,11 +371,11 @@ class Order(models.Model):
if self.coupon.discount_value_type == DiscountValueType.FIXED:
return self.coupon.discount_value
elif self.coupon.discount_value_type == DiscountValueType.PERCENTAGE:
- return (self.coupon.discount_value / Decimal('100')) * self.total_net_amount
+ return (self.coupon.discount_value / Decimal('100')) * self.subtotal_amount
return Decimal('0')
def get_total_price_after_discount(self):
- return round((self.total_net_amount - self.get_discount()) + self.shipping_total, 2)
+ return round((self.subtotal_amount - self.get_discount()) + self.shipping_total, 2)
def get_absolute_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.pk})
@@ -294,13 +405,13 @@ class Transaction(models.Model):
class OrderLine(models.Model):
order = models.ForeignKey(
Order,
- related_name="lines",
+ related_name='lines',
editable=False,
on_delete=models.CASCADE
)
- product = models.ForeignKey(
- Product,
- related_name="order_lines",
+ variant = models.ForeignKey(
+ ProductVariant,
+ related_name='order_lines',
on_delete=models.SET_NULL,
blank=True,
null=True,
@@ -309,20 +420,17 @@ class OrderLine(models.Model):
quantity_fulfilled = models.IntegerField(
validators=[MinValueValidator(0)], default=0
)
- customer_note = models.TextField(blank=True, default="")
-
+ customer_note = models.TextField(blank=True, default='')
currency = models.CharField(
max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH,
default=settings.DEFAULT_CURRENCY,
)
-
unit_price = models.DecimalField(
max_digits=settings.DEFAULT_MAX_DIGITS,
decimal_places=settings.DEFAULT_DECIMAL_PLACES,
)
-
tax_rate = models.DecimalField(
- max_digits=5, decimal_places=2, default=Decimal("0.0")
+ max_digits=5, decimal_places=2, default=Decimal('0.0')
)
def get_total(self):
diff --git a/src/core/tests/test_models.py b/src/core/tests/test_models.py
index 59935f9..841f14f 100644
--- a/src/core/tests/test_models.py
+++ b/src/core/tests/test_models.py
@@ -7,7 +7,7 @@ from core.models import (
Product,
ProductPhoto,
Coupon,
- ShippingMethod,
+ ShippingRate,
Order,
Transaction,
OrderLine,
diff --git a/src/core/weight.py b/src/core/weight.py
index baf9dd0..a3a6dc3 100644
--- a/src/core/weight.py
+++ b/src/core/weight.py
@@ -1,14 +1,15 @@
from measurement.measures import Weight
+
class WeightUnits:
# KILOGRAM = "kg"
- # POUND = "lb"
+ POUND = "lb"
OUNCE = "oz"
# GRAM = "g"
CHOICES = [
# (KILOGRAM, "kg"),
- # (POUND, "lb"),
+ (POUND, "lb"),
(OUNCE, "oz"),
# (GRAM, "g"),
]
diff --git a/src/dashboard/forms.py b/src/dashboard/forms.py
index 56aaa53..3d75eff 100644
--- a/src/dashboard/forms.py
+++ b/src/dashboard/forms.py
@@ -5,7 +5,7 @@ from core import OrderStatus
from core.models import (
Order,
OrderLine,
- ShippingMethod,
+ ShippingRate,
TrackingNumber,
Coupon,
ProductPhoto
diff --git a/src/dashboard/templates/dashboard/catalog.html b/src/dashboard/templates/dashboard/catalog.html
new file mode 100644
index 0000000..99bfced
--- /dev/null
+++ b/src/dashboard/templates/dashboard/catalog.html
@@ -0,0 +1,76 @@
+{% extends "dashboard.html" %}
+{% load static %}
+
+{% block content %}
+ Is a main category: {{ category.main_category|yesno:"Yes,No" }} No products
- {{method.name}} | {{method.type}} | {{method.price}}
+ {{ rate }}
No shipping methods yet. No shipping rates yet.
+
+
Catalog
+
Uncategorized Products
+
+
Product Options
+
+
{{ category }}Create category
+
+
{{ category.name }}Update category
+
+
+
CategoriesStaff
- + New staff
- Option
+ Create option
+ {{ option.name }}
+
+ Products
+
Order #{{order.pk}}
- Subtotal: ${{order.total_net_amount}}
+ Subtotal: ${{order.subtotal_amount}}
{% if order.coupon %}
Discount: {{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}
{% endif %}
diff --git a/src/dashboard/templates/dashboard/order_fulfill.html b/src/dashboard/templates/dashboard/order_fulfill.html
index 2e7b351..339947d 100644
--- a/src/dashboard/templates/dashboard/order_fulfill.html
+++ b/src/dashboard/templates/dashboard/order_fulfill.html
@@ -7,7 +7,6 @@
Category: {{ product.category }}
{{product.description}}
-${{product.price}}
-{{product.weight.oz}} oz
+Checkout limit: {{ product.checkout_limit }}
Visible in listings: {{product.visible_in_listings|yesno:"Yes,No"}}
-Stripe ID: {{ product.stripe_id }}
+Sorting: {{ product.sorting }}
Ordered {{product.num_ordered|default_if_none:"0"}} time{{ product.num_ordered|default_if_none:"0"|pluralize }}.
SKU: {{ variant.sku }}
+Price: ${{ variant.price }}
+Weight: {{ variant.weight }}
+ {% if variant.track_inventory %} +Stock: {{ variant.stock }}
+ {% endif %} + +To create more product options go to the catalog
+{{ option.options }}
+
Catalog
{{ rate }}
Shipping RateShipping Provider: {{ rate.shipping_provider }}
+Container: {{ rate.get_container_display }}
+Weight range: {{ rate.min_order_weight }} – {{ rate.max_order_weight }}
+
Shipping Method{{shippingmethod.get_type_display}}
-${{shippingmethod.price}}
-{{ key|get_grind_display }}
-
{{ key }}: {{ value }}
{% endfor %} + +${{item.price}}
+
+ ${{ item.variant.price }}
+ {% if cart.coupon and cart.coupon.type == 'specific_product' and product in cart.coupon.products.all %}
+
Coupon: {{ cart.coupon.name }} ({{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}})
+ {% endif %}
+
{{product.description|truncatewords:20}}
+${{product.variants.first.price}}
+diff --git a/src/storefront/templates/storefront/order_detail.html b/src/storefront/templates/storefront/order_detail.html index 7c0d48c..d5eb3ca 100644 --- a/src/storefront/templates/storefront/order_detail.html +++ b/src/storefront/templates/storefront/order_detail.html @@ -21,12 +21,12 @@
| Subtotal | -${{order.total_net_amount}} | +${{order.subtotal}} |
| Subtotal | ${{cart.get_total_price|floatformat:"2"}} | |
| Coupon | {{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}} | diff --git a/src/storefront/templates/storefront/product_detail.html b/src/storefront/templates/storefront/product_detail.html index 3594102..d88686f 100644 --- a/src/storefront/templates/storefront/product_detail.html +++ b/src/storefront/templates/storefront/product_detail.html @@ -21,9 +21,6 @@