245 lines
6.5 KiB
Python
245 lines
6.5 KiB
Python
import logging
|
|
from decimal import Decimal
|
|
from PIL import Image
|
|
from measurement.measures import Weight
|
|
|
|
from django.db import models
|
|
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.validators import MinValueValidator, MaxValueValidator
|
|
|
|
from django_measurement.models import MeasurementField
|
|
|
|
from accounts.models import User, Address
|
|
|
|
from . import (
|
|
DiscountValueType,
|
|
VoucherType,
|
|
TransactionStatus,
|
|
OrderStatus,
|
|
ShippingMethodType
|
|
)
|
|
from .weight import WeightUnits, zero_weight
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ProductManager(models.Manager):
|
|
def get_queryset(self):
|
|
return super().get_queryset().annotate(
|
|
num_ordered=models.Sum('order_lines__quantity')
|
|
)
|
|
|
|
|
|
|
|
class Product(models.Model):
|
|
name = models.CharField(max_length=250)
|
|
description = models.TextField(blank=True)
|
|
sku = models.CharField(max_length=255, unique=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
|
|
)
|
|
|
|
visible_in_listings = models.BooleanField(default=False)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
objects = ProductManager()
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class ProductPhoto(models.Model):
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
|
image = models.ImageField(upload_to='products/images')
|
|
|
|
def __str__(self):
|
|
return self.product.name
|
|
|
|
# def save(self, *args, **kwargs):
|
|
# super().save(*args, **kwargs)
|
|
|
|
# img = Image.open(self.image.path)
|
|
# if img.height > 400 or img.width > 400:
|
|
# output_size = (400, 400)
|
|
# img.thumbnail(output_size)
|
|
# img.save(self.image.path)
|
|
|
|
|
|
|
|
|
|
class Coupon(models.Model):
|
|
type = models.CharField(
|
|
max_length=20, choices=VoucherType.CHOICES, default=VoucherType.ENTIRE_ORDER
|
|
)
|
|
name = models.CharField(max_length=255, null=True, blank=True)
|
|
code = models.CharField(max_length=12, unique=True, db_index=True)
|
|
valid_from = models.DateTimeField(default=timezone.now)
|
|
valid_to = models.DateTimeField(null=True, blank=True)
|
|
|
|
discount_value_type = models.CharField(
|
|
max_length=10,
|
|
choices=DiscountValueType.CHOICES,
|
|
default=DiscountValueType.FIXED,
|
|
)
|
|
discount_value = models.DecimalField(
|
|
max_digits=settings.DEFAULT_MAX_DIGITS,
|
|
decimal_places=settings.DEFAULT_DECIMAL_PLACES,
|
|
)
|
|
|
|
products = models.ManyToManyField(Product, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ("code",)
|
|
|
|
@property
|
|
def is_valid(self):
|
|
today = timezone.localtime(timezone.now()).date()
|
|
return True if today >= self.valid_from and today <= self.valid_to else False
|
|
|
|
|
|
|
|
|
|
class ShippingMethod(models.Model):
|
|
name = models.CharField(max_length=100)
|
|
type = models.CharField(max_length=30, choices=ShippingMethodType.CHOICES)
|
|
|
|
|
|
class OrderManager(models.Manager):
|
|
def with_lines(self):
|
|
return self.select_related('lines')
|
|
|
|
def with_fulfillment(self):
|
|
return self.annotate(
|
|
total_quantity_fulfilled=models.Sum('lines__quantity_fulfilled'),
|
|
total_quantity_ordered=models.Sum('lines__quantity')
|
|
)
|
|
|
|
|
|
class Order(models.Model):
|
|
customer = models.ForeignKey(
|
|
User,
|
|
related_name="orders",
|
|
on_delete=models.SET_NULL,
|
|
null=True
|
|
)
|
|
status = models.CharField(
|
|
max_length=32, default=OrderStatus.UNFULFILLED, choices=OrderStatus.CHOICES
|
|
)
|
|
billing_address = models.ForeignKey(
|
|
Address,
|
|
related_name="+",
|
|
editable=False,
|
|
null=True,
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
shipping_address = models.ForeignKey(
|
|
Address,
|
|
related_name="+",
|
|
editable=False,
|
|
null=True,
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
shipping_method = models.ForeignKey(
|
|
ShippingMethod,
|
|
blank=True,
|
|
null=True,
|
|
related_name="orders",
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
|
|
total_net_amount = models.DecimalField(
|
|
max_digits=10,
|
|
decimal_places=2,
|
|
default=0,
|
|
)
|
|
|
|
|
|
weight = MeasurementField(
|
|
measurement=Weight,
|
|
unit_choices=WeightUnits.CHOICES,
|
|
default=zero_weight,
|
|
)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, editable=False)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
objects = OrderManager()
|
|
|
|
def get_total_quantity(self):
|
|
return sum([line.quantity for line in self])
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('dashboard:order-detail', kwargs={'pk': self.pk})
|
|
|
|
|
|
|
|
class Transaction(models.Model):
|
|
status = models.CharField(
|
|
max_length=32,
|
|
blank=True,
|
|
default=TransactionStatus.CREATED,
|
|
choices=TransactionStatus.CHOICES
|
|
)
|
|
paypal_id = models.CharField(max_length=64, blank=True)
|
|
confirmation_email_sent = models.BooleanField(default=False)
|
|
order = models.OneToOneField(
|
|
Order,
|
|
editable=False,
|
|
blank=True,
|
|
null=True,
|
|
on_delete=models.CASCADE
|
|
)
|
|
|
|
|
|
class OrderLine(models.Model):
|
|
order = models.ForeignKey(
|
|
Order,
|
|
related_name="lines",
|
|
editable=False,
|
|
on_delete=models.CASCADE
|
|
)
|
|
product = models.ForeignKey(
|
|
Product,
|
|
related_name="order_lines",
|
|
on_delete=models.SET_NULL,
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
quantity = models.IntegerField(validators=[MinValueValidator(1)])
|
|
quantity_fulfilled = models.IntegerField(
|
|
validators=[MinValueValidator(0)], default=0
|
|
)
|
|
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")
|
|
)
|
|
|
|
def get_total(self):
|
|
return self.unit_price * self.quantity
|
|
|
|
@property
|
|
def quantity_unfulfilled(self):
|
|
return self.quantity - self.quantity_fulfilled
|