Add basic product variant display to detail page
This commit is contained in:
parent
27a0f94c4e
commit
8dfce8e92b
@ -66,13 +66,15 @@ class TransactionStatus:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ShippingMethodType:
|
class ShippingProvider:
|
||||||
PRICE_BASED = 'price'
|
USPS = 'USPS'
|
||||||
WEIGHT_BASED = 'weight'
|
# UPS = 'UPS'
|
||||||
|
# FEDEX = 'FEDEX'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(PRICE_BASED, 'Price based shipping'),
|
(USPS, 'USPS'),
|
||||||
(WEIGHT_BASED, 'Weight based shipping'),
|
# (UPS, 'UPS'),
|
||||||
|
# (FEDEX, 'FedEx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,25 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
|
ProductCategory,
|
||||||
Product,
|
Product,
|
||||||
ProductPhoto,
|
ProductPhoto,
|
||||||
|
ProductVariant,
|
||||||
|
ProductOption,
|
||||||
Coupon,
|
Coupon,
|
||||||
ShippingMethod,
|
ShippingRate,
|
||||||
Order,
|
Order,
|
||||||
Transaction,
|
Transaction,
|
||||||
OrderLine,
|
OrderLine,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
admin.site.register(ProductCategory)
|
||||||
admin.site.register(Product)
|
admin.site.register(Product)
|
||||||
admin.site.register(ProductPhoto)
|
admin.site.register(ProductPhoto)
|
||||||
|
admin.site.register(ProductVariant)
|
||||||
|
admin.site.register(ProductOption)
|
||||||
admin.site.register(Coupon)
|
admin.site.register(Coupon)
|
||||||
admin.site.register(ShippingMethod)
|
admin.site.register(ShippingRate)
|
||||||
admin.site.register(Order)
|
admin.site.register(Order)
|
||||||
admin.site.register(Transaction)
|
admin.site.register(Transaction)
|
||||||
admin.site.register(OrderLine)
|
admin.site.register(OrderLine)
|
||||||
|
|||||||
5
src/core/context_processors.py
Normal file
5
src/core/context_processors.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .models import SiteSettings
|
||||||
|
|
||||||
|
|
||||||
|
def site_settings(request):
|
||||||
|
return {'site_settings': SiteSettings.load()}
|
||||||
@ -2,11 +2,11 @@ import logging
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
from core.models import Order, OrderLine, ShippingMethod
|
from core.models import Order, OrderLine, ShippingRate
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ShippingMethodForm(forms.ModelForm):
|
class ShippingRateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ShippingMethod
|
model = ShippingRate
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|||||||
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
src/core/migrations/0012_alter_productvariant_product.py
Normal file
19
src/core/migrations/0012_alter_productvariant_product.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -8,8 +8,10 @@ from django.db.models.functions import Coalesce
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.core.cache import cache
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
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.forms.models import model_to_dict
|
||||||
|
|
||||||
from django_measurement.models import MeasurementField
|
from django_measurement.models import MeasurementField
|
||||||
@ -21,17 +23,58 @@ from . import (
|
|||||||
VoucherType,
|
VoucherType,
|
||||||
TransactionStatus,
|
TransactionStatus,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
ShippingMethodType
|
ShippingProvider,
|
||||||
|
ShippingContainer
|
||||||
)
|
)
|
||||||
from .weight import WeightUnits, zero_weight
|
from .weight import WeightUnits, zero_weight
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ProductEncoder(DjangoJSONEncoder):
|
class SingletonBase(models.Model):
|
||||||
def default(self, obj):
|
def set_cache(self):
|
||||||
logger.info(f"\n{obj}\n")
|
cache.set(self.__class__.__name__, self)
|
||||||
return super().default(obj)
|
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Product Category'
|
||||||
|
verbose_name_plural = 'Product Categories'
|
||||||
|
|
||||||
|
|
||||||
class ProductManager(models.Manager):
|
class ProductManager(models.Manager):
|
||||||
@ -42,22 +85,18 @@ class ProductManager(models.Manager):
|
|||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
|
category = models.ForeignKey(
|
||||||
|
ProductCategory,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
name = models.CharField(max_length=250)
|
name = models.CharField(max_length=250)
|
||||||
subtitle = models.CharField(max_length=250, blank=True)
|
subtitle = models.CharField(max_length=250, blank=True)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
sku = models.CharField(max_length=255, unique=True)
|
checkout_limit = models.IntegerField(
|
||||||
stripe_id = models.CharField(max_length=255, blank=True)
|
default=0,
|
||||||
price = models.DecimalField(
|
validators=[MinValueValidator(0)]
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
visible_in_listings = models.BooleanField(default=False)
|
visible_in_listings = models.BooleanField(default=False)
|
||||||
@ -87,6 +126,61 @@ class Product(models.Model):
|
|||||||
ordering = ['sorting', 'name']
|
ordering = ['sorting', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.product}: {self.name}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProductOption(models.Model):
|
||||||
|
"""
|
||||||
|
Description: Consistent accross all variants
|
||||||
|
"""
|
||||||
|
product = models.ManyToManyField(
|
||||||
|
Product,
|
||||||
|
related_name='options'
|
||||||
|
)
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
options = ArrayField(
|
||||||
|
models.CharField(max_length=255)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}'
|
||||||
|
|
||||||
|
|
||||||
class ProductPhoto(models.Model):
|
class ProductPhoto(models.Model):
|
||||||
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
||||||
image = models.ImageField(upload_to='products/images')
|
image = models.ImageField(upload_to='products/images')
|
||||||
@ -149,17 +243,32 @@ class Coupon(models.Model):
|
|||||||
return reverse('dashboard:coupon-detail', kwargs={'pk': self.pk})
|
return reverse('dashboard:coupon-detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class ShippingMethod(models.Model):
|
class ShippingRate(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
shipping_provider = models.CharField(
|
||||||
type = models.CharField(max_length=30, choices=ShippingMethodType.CHOICES)
|
max_length=255,
|
||||||
price = models.DecimalField(
|
choices=ShippingProvider.CHOICES,
|
||||||
max_digits=settings.DEFAULT_MAX_DIGITS,
|
default=ShippingProvider.USPS
|
||||||
decimal_places=settings.DEFAULT_DECIMAL_PLACES,
|
)
|
||||||
default=0,
|
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):
|
def __str__(self):
|
||||||
return reverse('dashboard:shipmeth-detail', kwargs={'pk': self.pk})
|
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):
|
class OrderManager(models.Manager):
|
||||||
@ -210,13 +319,6 @@ class Order(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
on_delete=models.SET_NULL
|
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 = models.ForeignKey(
|
||||||
Coupon,
|
Coupon,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from core.models import (
|
|||||||
Product,
|
Product,
|
||||||
ProductPhoto,
|
ProductPhoto,
|
||||||
Coupon,
|
Coupon,
|
||||||
ShippingMethod,
|
ShippingRate,
|
||||||
Order,
|
Order,
|
||||||
Transaction,
|
Transaction,
|
||||||
OrderLine,
|
OrderLine,
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
from measurement.measures import Weight
|
from measurement.measures import Weight
|
||||||
|
|
||||||
|
|
||||||
class WeightUnits:
|
class WeightUnits:
|
||||||
# KILOGRAM = "kg"
|
# KILOGRAM = "kg"
|
||||||
# POUND = "lb"
|
POUND = "lb"
|
||||||
OUNCE = "oz"
|
OUNCE = "oz"
|
||||||
# GRAM = "g"
|
# GRAM = "g"
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
# (KILOGRAM, "kg"),
|
# (KILOGRAM, "kg"),
|
||||||
# (POUND, "lb"),
|
(POUND, "lb"),
|
||||||
(OUNCE, "oz"),
|
(OUNCE, "oz"),
|
||||||
# (GRAM, "g"),
|
# (GRAM, "g"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from core import OrderStatus
|
|||||||
from core.models import (
|
from core.models import (
|
||||||
Order,
|
Order,
|
||||||
OrderLine,
|
OrderLine,
|
||||||
ShippingMethod,
|
ShippingRate,
|
||||||
TrackingNumber,
|
TrackingNumber,
|
||||||
Coupon,
|
Coupon,
|
||||||
ProductPhoto
|
ProductPhoto
|
||||||
|
|||||||
@ -20,8 +20,8 @@ from dashboard.forms import (
|
|||||||
from dashboard.views import (
|
from dashboard.views import (
|
||||||
DashboardHomeView,
|
DashboardHomeView,
|
||||||
DashboardConfigView,
|
DashboardConfigView,
|
||||||
ShippingMethodCreateView,
|
ShippingRateCreateView,
|
||||||
ShippingMethodDetailView,
|
ShippingRateDetailView,
|
||||||
CouponListView,
|
CouponListView,
|
||||||
CouponCreateView,
|
CouponCreateView,
|
||||||
CouponDetailView,
|
CouponDetailView,
|
||||||
|
|||||||
@ -15,13 +15,13 @@ urlpatterns = [
|
|||||||
|
|
||||||
path(
|
path(
|
||||||
'shipping-methods/new/',
|
'shipping-methods/new/',
|
||||||
views.ShippingMethodCreateView.as_view(),
|
views.ShippingRateCreateView.as_view(),
|
||||||
name='shipmeth-create'
|
name='shipmeth-create'
|
||||||
),
|
),
|
||||||
path('shipping-methods/<int:pk>/', include([
|
path('shipping-methods/<int:pk>/', include([
|
||||||
path(
|
path(
|
||||||
'',
|
'',
|
||||||
views.ShippingMethodDetailView.as_view(),
|
views.ShippingRateDetailView.as_view(),
|
||||||
name='shipmeth-detail'
|
name='shipmeth-detail'
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
|
|||||||
@ -30,7 +30,7 @@ from core.models import (
|
|||||||
ProductPhoto,
|
ProductPhoto,
|
||||||
Order,
|
Order,
|
||||||
OrderLine,
|
OrderLine,
|
||||||
ShippingMethod,
|
ShippingRate,
|
||||||
Transaction,
|
Transaction,
|
||||||
TrackingNumber,
|
TrackingNumber,
|
||||||
Coupon
|
Coupon
|
||||||
@ -39,8 +39,7 @@ from core.models import (
|
|||||||
from core import (
|
from core import (
|
||||||
DiscountValueType,
|
DiscountValueType,
|
||||||
VoucherType,
|
VoucherType,
|
||||||
OrderStatus,
|
OrderStatus
|
||||||
ShippingMethodType
|
|
||||||
)
|
)
|
||||||
from .forms import (
|
from .forms import (
|
||||||
OrderLineFulfillForm,
|
OrderLineFulfillForm,
|
||||||
@ -83,20 +82,20 @@ class DashboardConfigView(TemplateView):
|
|||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
today = timezone.localtime(timezone.now()).date()
|
today = timezone.localtime(timezone.now()).date()
|
||||||
|
|
||||||
context['shipping_method_list'] = ShippingMethod.objects.all()
|
context['shipping_method_list'] = ShippingRate.objects.all()
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ShippingMethodCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
class ShippingRateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
model = ShippingMethod
|
model = ShippingRate
|
||||||
template_name = 'dashboard/shipmeth_create_form.html'
|
template_name = 'dashboard/shipmeth_create_form.html'
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
success_message = '%(name)s created.'
|
success_message = '%(name)s created.'
|
||||||
|
|
||||||
|
|
||||||
class ShippingMethodDetailView(LoginRequiredMixin, DetailView):
|
class ShippingRateDetailView(LoginRequiredMixin, DetailView):
|
||||||
model = ShippingMethod
|
model = ShippingRate
|
||||||
template_name = 'dashboard/shipmeth_detail.html'
|
template_name = 'dashboard/shipmeth_detail.html'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -85,6 +85,7 @@ TEMPLATES = [
|
|||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'core.context_processors.site_settings',
|
||||||
'storefront.context_processors.cart',
|
'storefront.context_processors.cart',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,7 +14,6 @@ from core import (
|
|||||||
VoucherType,
|
VoucherType,
|
||||||
TransactionStatus,
|
TransactionStatus,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
ShippingMethodType,
|
|
||||||
ShippingService,
|
ShippingService,
|
||||||
ShippingContainer,
|
ShippingContainer,
|
||||||
CoffeeGrind
|
CoffeeGrind
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from .cart import Cart
|
from .cart import Cart
|
||||||
|
|
||||||
|
|
||||||
def cart(request):
|
def cart(request):
|
||||||
return {
|
return {
|
||||||
'cart': Cart(request)
|
'cart': Cart(request)
|
||||||
|
|||||||
@ -18,9 +18,20 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class AddToCartForm(forms.Form):
|
class AddToCartForm(forms.Form):
|
||||||
grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES)
|
# grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES)
|
||||||
|
variants = forms.ChoiceField(widget=forms.RadioSelect())
|
||||||
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
||||||
|
|
||||||
|
def __init__(self, variants, options, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
self.fields[option.name] = forms.ChoiceField(
|
||||||
|
choices=[(opt, opt) for opt in option.options]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.fields['variants'].widget.choices = [(variant.pk, variant.name) for variant in variants]
|
||||||
|
|
||||||
|
|
||||||
class UpdateCartItemForm(forms.Form):
|
class UpdateCartItemForm(forms.Form):
|
||||||
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
||||||
|
|||||||
@ -31,8 +31,10 @@ from accounts.utils import get_or_create_customer
|
|||||||
from accounts.forms import (
|
from accounts.forms import (
|
||||||
AddressForm as AccountAddressForm, CustomerUpdateForm
|
AddressForm as AccountAddressForm, CustomerUpdateForm
|
||||||
)
|
)
|
||||||
from core.models import Product, Order, Transaction, OrderLine, Coupon
|
from core.models import (
|
||||||
from core.forms import ShippingMethodForm
|
Product, ProductOption, Order, Transaction, OrderLine, Coupon
|
||||||
|
)
|
||||||
|
from core.forms import ShippingRateForm
|
||||||
from core import OrderStatus, ShippingContainer
|
from core import OrderStatus, ShippingContainer
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
@ -161,6 +163,13 @@ class ProductDetailView(FormMixin, DetailView):
|
|||||||
template_name = 'storefront/product_detail.html'
|
template_name = 'storefront/product_detail.html'
|
||||||
form_class = AddToCartForm
|
form_class = AddToCartForm
|
||||||
|
|
||||||
|
def get_form(self, form_class=None):
|
||||||
|
variants = self.object.variants.all()
|
||||||
|
options = ProductOption.objects.filter(product__pk=self.object.pk)
|
||||||
|
if form_class is None:
|
||||||
|
form_class = self.get_form_class()
|
||||||
|
return form_class(variants, options, **self.get_form_kwargs())
|
||||||
|
|
||||||
|
|
||||||
class CheckoutAddressView(FormView):
|
class CheckoutAddressView(FormView):
|
||||||
template_name = 'storefront/checkout_address.html'
|
template_name = 'storefront/checkout_address.html'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user