2023-01-21 14:15:36 -07:00

663 lines
20 KiB
Python

import logging
from datetime import datetime
from django.conf import settings
from django.utils import timezone
from django import forms
from django.shortcuts import render, reverse, redirect, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.edit import (
FormView, CreateView, UpdateView, DeleteView, FormMixin
)
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.generic.list import ListView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import (
LoginRequiredMixin, PermissionRequiredMixin
)
from django.forms import inlineformset_factory
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import (
Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value,
ExpressionWrapper, IntegerField
)
from django.db.models.functions import Coalesce
from accounts.models import User
from accounts.utils import get_or_create_customer
from accounts.forms import AddressForm
from core.models import (
ProductCategory,
Product,
ProductPhoto,
ProductVariant,
ProductOption,
Order,
OrderLine,
ShippingRate,
Transaction,
TrackingNumber,
Coupon,
SiteSettings
)
from core import (
DiscountValueType,
VoucherType,
OrderStatus
)
from .forms import (
ProductVariantUpdateForm,
OrderLineFulfillForm,
OrderLineFormset,
OrderCancelForm,
OrderTrackingFormset,
CouponForm,
ProductPhotoForm
)
logger = logging.getLogger(__name__)
class DashboardHomeView(LoginRequiredMixin, TemplateView):
template_name = 'dashboard/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
today = timezone.localtime(timezone.now()).date()
context['order_count'] = Order.objects.exclude(
status=OrderStatus.DRAFT
).filter(
created_at__date=today
).count()
context['orders_unfulfilled'] = Order.objects.filter(
status=OrderStatus.UNFULFILLED
).count()
context['todays_sales'] = Order.objects.exclude(
status=OrderStatus.DRAFT
).filter(
created_at__date=today
).aggregate(total=Sum('total_amount'))['total']
return context
class DashboardConfigView(LoginRequiredMixin, TemplateView):
template_name = 'dashboard/config.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['shipping_rate_list'] = ShippingRate.objects.all()
return context
class SiteSettingsUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
permission_required = 'core.change_sitesettings'
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(LoginRequiredMixin, ListView):
model = ProductCategory
context_object_name = 'category_list'
template_name = 'dashboard/catalog.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['uncategorized_products'] = Product.objects.filter(
category=None
)
context['option_list'] = ProductOption.objects.all()
return context
class StockView(LoginRequiredMixin, ListView):
model = ProductVariant
context_object_name = 'variant_list'
template_name = 'dashboard/stock.html'
def get_queryset(self):
object_list = ProductVariant.objects.filter(
track_inventory=True
).prefetch_related('order_lines', 'product').annotate(
total_in_warehouse=F('stock') + Coalesce(Sum('order_lines__quantity', filter=Q(
order_lines__order__status=OrderStatus.UNFULFILLED) | Q(
order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED)
) - Sum('order_lines__quantity_fulfilled', filter=Q(
order_lines__order__status=OrderStatus.UNFULFILLED) | Q(
order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED)), 0)
).order_by('product')
return object_list
class ShippingRateDetailView(LoginRequiredMixin, DetailView):
model = ShippingRate
context_object_name = 'rate'
template_name = 'dashboard/rate/detail.html'
class ShippingRateCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_shippingrate'
model = ShippingRate
context_object_name = 'rate'
template_name = 'dashboard/rate/create_form.html'
fields = '__all__'
success_message = '%(name)s created.'
class ShippingRateUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.change_shippingrate'
model = ShippingRate
context_object_name = 'rate'
template_name = 'dashboard/rate/form.html'
success_message = 'ShippingRate saved.'
fields = '__all__'
class ShippingRateDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView
):
permission_required = 'core.delete_shippingrate'
model = ShippingRate
context_object_name = 'rate'
template_name = 'dashboard/rate/confirm_delete.html'
success_message = 'ShippingRate deleted.'
success_url = reverse_lazy('dashboard:config')
class CouponListView(LoginRequiredMixin, ListView):
model = Coupon
template_name = 'dashboard/coupon/list.html'
class CouponCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_coupon'
model = Coupon
template_name = 'dashboard/coupon/create_form.html'
form_class = CouponForm
success_message = '%(name)s created.'
class CouponDetailView(LoginRequiredMixin, DetailView):
model = Coupon
template_name = 'dashboard/coupon/detail.html'
class CouponUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.change_coupon'
model = Coupon
template_name = 'dashboard/coupon/form.html'
success_message = '%(name)s saved.'
form_class = CouponForm
class CouponDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView
):
permission_required = 'core.delete_coupon'
model = Coupon
template_name = 'dashboard/coupon/confirm_delete.html'
success_url = reverse_lazy('dashboard:coupon-list')
success_message = 'Coupon deleted.'
class OrderListView(LoginRequiredMixin, ListView):
model = Order
template_name = 'dashboard/order/list.html'
paginate_by = 50
def get_queryset(self):
query = self.request.GET.get('status')
if query == 'unfulfilled':
object_list = Order.objects.filter(
Q(status=OrderStatus.UNFULFILLED) |
Q(status=OrderStatus.PARTIALLY_FULFILLED)
).order_by('-created_at').select_related('customer')
else:
object_list = Order.objects.order_by(
'-created_at'
).select_related('customer')
return object_list
class OrderDetailView(LoginRequiredMixin, DetailView):
model = Order
template_name = 'dashboard/order/detail.html'
def get_object(self):
queryset = Order.objects.with_fulfillment_and_filter(
self.kwargs.get(self.pk_url_kwarg)
).select_related(
'customer',
'billing_address',
'shipping_address'
).prefetch_related(
'lines__variant__product__productphoto_set'
)
obj = queryset.get()
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
class OrderFulfillView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = Order
template_name = 'dashboard/order/fulfill.html'
form_class = OrderLineFormset
success_message = 'Order saved.'
def form_valid(self, form):
form.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
class OrderCancelView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.cancel_order'
model = Order
template_name = 'dashboard/order/cancel_form.html'
form_class = OrderCancelForm
success_message = 'Order canceled.'
initial = {
'status': OrderStatus.CANCELED
}
def form_valid(self, form):
form.instance.add_stock()
form.instance.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
class OrderTrackingView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = Order
template_name = 'dashboard/order/tracking_form.html'
form_class = OrderTrackingFormset
success_message = 'Order saved.'
def form_valid(self, form):
form.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
class CategoryListView(LoginRequiredMixin, ListView):
model = ProductCategory
context_object_name = 'category_list'
template_name = 'dashboard/category/list.html'
class CategoryCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_productcategory'
model = ProductCategory
context_object_name = 'category'
success_message = 'Category created.'
template_name = 'dashboard/category/create_form.html'
fields = '__all__'
class CategoryDetailView(LoginRequiredMixin, DetailView):
model = ProductCategory
context_object_name = 'category'
template_name = 'dashboard/category/detail.html'
class CategoryUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.change_productcategory'
model = ProductCategory
context_object_name = 'category'
success_message = 'Category saved.'
template_name = 'dashboard/category/form.html'
fields = '__all__'
class CategoryDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView
):
permission_required = 'core.delete_productcategory'
model = ProductCategory
context_object_name = 'category'
success_message = 'Category deleted.'
template_name = 'dashboard/category/confirm_delete.html'
success_url = reverse_lazy('dashboard:catalog')
class ProductListView(LoginRequiredMixin, ListView):
model = Product
template_name = 'dashboard/product/list.html'
ordering = 'sorting'
# def get_queryset(self):
# object_list = Product.objects.filter(
# status=OrderStatus.UNFULFILLED
# ).select_related(
# 'customer'
# )
# return object_list
class ProductDetailView(LoginRequiredMixin, DetailView):
model = Product
template_name = 'dashboard/product/detail.html'
def get_object(self):
pk = self.kwargs.get(self.pk_url_kwarg)
queryset = Product.objects.filter(
pk=pk
).select_related(
'category',
).prefetch_related(
'options',
'productphoto_set',
Prefetch(
'variants',
queryset=ProductVariant.objects.all().order_by('sorting')
)
)
obj = queryset.get()
return obj
class ProductCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_product'
model = Product
template_name = 'dashboard/product/create_form.html'
fields = '__all__'
success_message = '%(name)s created.'
class ProductUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.change_product'
model = Product
template_name = 'dashboard/product/form.html'
fields = '__all__'
success_message = '%(name)s saved.'
class ProductDeleteView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
DeleteView
):
permission_required = 'core.delete_product'
model = Product
template_name = 'dashboard/product/confirm_delete.html'
success_url = reverse_lazy('dashboard:catalog')
success_message = 'Product deleted.'
class ProductPhotoCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_productphoto'
model = ProductPhoto
pk_url_kwarg = 'photo_pk'
template_name = 'dashboard/product/photo_create_form.html'
form_class = ProductPhotoForm
success_message = 'Photo added.'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.product = Product.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class ProductPhotoDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView
):
permission_required = 'core.delete_productphoto'
model = ProductPhoto
pk_url_kwarg = 'photo_pk'
template_name = 'dashboard/prodphoto_confirm_delete.html'
success_message = 'Photo deleted.'
def get_success_url(self):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class ProductVariantCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_productvariant'
model = ProductVariant
success_message = 'Variant created.'
template_name = 'dashboard/variant/create_form.html'
fields = [
'name',
'sku',
'price',
'weight',
'visible_in_listings',
'track_inventory',
'stock',
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.product = Product.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class ProductVariantUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.change_productvariant'
model = ProductVariant
pk_url_kwarg = 'variant_pk'
success_message = 'Variant saved.'
template_name = 'dashboard/variant/form.html'
form_class = ProductVariantUpdateForm
context_object_name = 'variant'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.product = Product.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class ProductVariantDeleteView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
DeleteView
):
permission_required = 'core.delete_productvariant'
model = ProductVariant
pk_url_kwarg = 'variant_pk'
success_message = 'ProductVariant deleted.'
template_name = 'dashboard/variant/confirm_delete.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(pk=self.kwargs['pk'])
return context
def get_success_url(self):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class ProductVariantStockUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
permission_required = 'core.change_productvariant'
model = ProductVariant
pk_url_kwarg = 'variant_pk'
success_message = 'ProductVariant saved.'
success_url = reverse_lazy('dashboard:stock')
template_name = 'dashboard/variant/restock.html'
fields = [
'stock',
]
context_object_name = 'variant'
def get_queryset(self):
queryset = ProductVariant.objects.annotate(
total_in_warehouse=F('stock') + Coalesce(Sum('order_lines__quantity', filter=Q(
order_lines__order__status=OrderStatus.UNFULFILLED) | Q(
order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED)
) - Sum('order_lines__quantity_fulfilled', filter=Q(
order_lines__order__status=OrderStatus.UNFULFILLED) | Q(
order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED)), 0)
).prefetch_related('order_lines', 'product')
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.product = Product.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
class ProductOptionDetailView(LoginRequiredMixin, DetailView):
model = ProductOption
template_name = 'dashboard/option/detail.html'
context_object_name = 'option'
class ProductOptionCreateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView
):
permission_required = 'core.add_productoption'
model = ProductOption
template_name = 'dashboard/option/create_form.html'
fields = [
'name',
'options',
'products',
]
success_message = '%(name)s created.'
class ProductOptionUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'core.change_productoption'
model = ProductOption
success_message = 'Option saved.'
template_name = 'dashboard/option/form.html'
fields = [
'name',
'options',
'products',
]
context_object_name = 'option'
success_url = reverse_lazy('dashboard:catalog')
class ProductOptionDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView
):
permission_required = 'core.delete_productoption'
model = ProductOption
success_message = 'ProductOption deleted.'
template_name = 'dashboard/option/confirm_delete.html'
context_object_name = 'option'
success_url = reverse_lazy('dashboard:catalog')
class CustomerListView(LoginRequiredMixin, ListView):
model = User
template_name = 'dashboard/customer/list.html'
paginate_by = 100
def get_queryset(self):
object_list = User.objects.filter(
Exists(
Order.objects.filter(customer=OuterRef('pk'))
) | Q(is_staff=False)
).prefetch_related(
'orders'
).annotate(
num_orders=Count('orders')
).order_by('first_name', 'last_name')
return object_list
class CustomerDetailView(LoginRequiredMixin, DetailView):
model = User
template_name = 'dashboard/customer/detail.html'
context_object_name = 'customer'
class CustomerUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
permission_required = 'accounts.change_user'
model = User
template_name = 'dashboard/customer/form.html'
context_object_name = 'customer'
success_message = 'Customer saved.'
fields = (
'first_name',
'last_name',
'email',
'is_staff',
'addresses',
'default_shipping_address'
)
def get_success_url(self):
return reverse('dashboard:customer-detail', kwargs={'pk': self.object.pk})