Add functionality to cancel an order from dashboard

This commit is contained in:
Nathan Chapman 2022-05-12 18:43:55 -06:00
parent d93f266861
commit 06d809f7f4
11 changed files with 4715 additions and 23 deletions

View File

@ -1,5 +1,6 @@
from django.conf import settings
class DiscountValueType:
FIXED = "fixed"
PERCENTAGE = "percentage"
@ -46,6 +47,7 @@ class OrderStatus:
(CANCELED, "Canceled"),
]
class TransactionStatus:
CREATED = "CREATED" # The order was created with the specified context.
SAVED = "SAVED" # The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order.
@ -63,6 +65,7 @@ class TransactionStatus:
(PAYER_ACTION_REQUIRED, "Payer action required")
]
class ShippingMethodType:
PRICE_BASED = "price"
WEIGHT_BASED = "weight"
@ -72,6 +75,7 @@ class ShippingMethodType:
(WEIGHT_BASED, "Weight based shipping"),
]
class ShippingService:
FIRST_CLASS = "FIRST CLASS"
PRIORITY = "PRIORITY"
@ -83,6 +87,7 @@ class ShippingService:
(PRIORITY_COMMERCIAL, "Priority Commercial")
]
class ShippingContainer:
LG_FLAT_RATE_BOX = "LG FLAT RATE BOX"
REGIONAL_RATE_BOX_A = "REGIONALRATEBOXA"

View File

@ -0,0 +1,129 @@
[
{
"model": "core.order",
"pk": 1,
"fields": {
"customer": null,
"status": "unfulfilled",
"billing_address": null,
"shipping_address": 1,
"shipping_method": null,
"coupon": null,
"shipping_total": "9.55",
"total_net_amount": "13.40",
"weight": "0.0:oz",
"created_at": "2022-03-15T17:18:59.584Z",
"updated_at": "2022-03-15T17:18:59.584Z"
}
}, {
"model": "core.order",
"pk": 2,
"fields": {
"customer": null,
"status": "unfulfilled",
"billing_address": null,
"shipping_address": 1,
"shipping_method": null,
"coupon": null,
"shipping_total": "9.55",
"total_net_amount": "13.40",
"weight": "0.0:oz",
"created_at": "2022-03-15T17:22:18.440Z",
"updated_at": "2022-03-15T17:22:18.440Z"
}
}, {
"model": "core.order",
"pk": 3,
"fields": {
"customer": null,
"status": "unfulfilled",
"billing_address": null,
"shipping_address": 1,
"shipping_method": null,
"coupon": null,
"shipping_total": "9.55",
"total_net_amount": "13.40",
"weight": "0.0:oz",
"created_at": "2022-03-15T17:26:27.869Z",
"updated_at": "2022-03-15T17:26:27.869Z"
}
}, {
"model": "core.orderline",
"pk": 1,
"fields": {
"order": 1,
"product": 1,
"quantity": 2,
"quantity_fulfilled": 0,
"customer_note": "Whole Beans",
"currency": "USD",
"unit_price": "13.40",
"tax_rate": "2.00"
}
}, {
"model": "core.orderline",
"pk": 2,
"fields": {
"order": 1,
"product": 1,
"quantity": 1,
"quantity_fulfilled": 1,
"customer_note": "Espresso",
"currency": "USD",
"unit_price": "13.40",
"tax_rate": "2.00"
}
}, {
"model": "core.orderline",
"pk": 3,
"fields": {
"order": 2,
"product": 8,
"quantity": 1,
"quantity_fulfilled": 1,
"customer_note": "Whole Beans",
"currency": "USD",
"unit_price": "13.40",
"tax_rate": "2.00"
}
}, {
"model": "core.orderline",
"pk": 4,
"fields": {
"order": 2,
"product": 7,
"quantity": 1,
"quantity_fulfilled": 1,
"customer_note": "Cone Drip",
"currency": "USD",
"unit_price": "13.40",
"tax_rate": "2.00"
}
}, {
"model": "core.orderline",
"pk": 5,
"fields": {
"order": 3,
"product": 4,
"quantity": 1,
"quantity_fulfilled": 0,
"customer_note": "Percolator",
"currency": "USD",
"unit_price": "13.40",
"tax_rate": "2.00"
}
}, {
"model": "core.orderline",
"pk": 6,
"fields": {
"order": 3,
"product": 6,
"quantity": 1,
"quantity_fulfilled": 0,
"customer_note": "Whole Beans",
"currency": "USD",
"unit_price": "13.40",
"tax_rate": "2.00"
}
}
]

View File

@ -1,10 +1,19 @@
import logging
from django import forms
from core.models import Order, OrderLine, ShippingMethod, TrackingNumber, Coupon, ProductPhoto
from core import OrderStatus
from core.models import (
Order,
OrderLine,
ShippingMethod,
TrackingNumber,
Coupon,
ProductPhoto
)
logger = logging.getLogger(__name__)
class CouponForm(forms.ModelForm):
class Meta:
model = Coupon
@ -19,10 +28,10 @@ class CouponForm(forms.ModelForm):
'products',
)
widgets = {
'valid_from': forms.DateInput(attrs = {
'valid_from': forms.DateInput(attrs={
'type': 'date'
}),
'valid_to': forms.DateInput(attrs = {
'valid_to': forms.DateInput(attrs={
'type': 'date'
}),
}
@ -35,7 +44,7 @@ class OrderLineFulfillForm(forms.ModelForm):
model = OrderLine
fields = ('quantity_fulfilled',)
widgets = {
'quantity_fulfilled': forms.NumberInput(attrs = {
'quantity_fulfilled': forms.NumberInput(attrs={
'min': 0,
})
}
@ -44,12 +53,22 @@ class OrderLineFulfillForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields['quantity_fulfilled'].widget.attrs['max'] = self.instance.quantity
OrderLineFormset = forms.inlineformset_factory(
Order, OrderLine, form=OrderLineFulfillForm,
extra=0, can_delete=False
)
class OrderCancelForm(forms.ModelForm):
class Meta:
model = Order
fields = ('status',)
widgets = {
'status': forms.HiddenInput()
}
class OrderTrackingForm(forms.ModelForm):
# send_shipment_details_to_customer = forms.BooleanField(initial=True)
@ -57,6 +76,7 @@ class OrderTrackingForm(forms.ModelForm):
model = TrackingNumber
fields = ('tracking_id',)
OrderTrackingFormset = forms.inlineformset_factory(
Order, TrackingNumber, form=OrderTrackingForm,
extra=1, can_delete=False

View File

@ -0,0 +1,17 @@
{% extends "dashboard.html" %}
{% block content %}
<article>
<header class="object__header">
<h1>Cancel Order #{{order.pk}}</h1>
</header>
<section class="object__panel">
<form method="POST" action="{% url 'dashboard:order-cancel' order.pk %}">
{% csrf_token %}
{{ form.as_p }}
<input class="action-button action-button--warning order__fulfill" type="submit" value="Cancel order"> or <a href="{% url 'dashboard:order-detail' order.pk %}">cancel</a>
</section>
</form>
</section>
</article>
{% endblock content %}

View File

@ -9,7 +9,7 @@
<div class="dropdown">
<span class="dropdown__menu">Options &darr;</span>
<div class="dropdown__child">
<a href="">Cancel order</a>
<a href="{% url 'dashboard:order-cancel' order.pk %}">Cancel order</a>
<a href="">Return order</a>
</div>
</div>

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

View File

@ -0,0 +1,80 @@
import logging
from decimal import Decimal
from django.test import TestCase, Client, RequestFactory
from django.urls import reverse
from django.conf import settings
from measurement.measures import Weight
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
from accounts.models import User, Address
from core.models import Product, Order, Coupon
from core import CoffeeGrind
from dashboard.forms import (
CouponForm,
OrderLineFulfillForm,
OrderTrackingForm,
ProductPhotoForm,
)
from dashboard.views import (
DashboardHomeView,
DashboardConfigView,
ShippingMethodCreateView,
ShippingMethodDetailView,
CouponListView,
CouponCreateView,
CouponDetailView,
CouponUpdateView,
CouponDeleteView,
OrderListView,
OrderDetailView,
OrderFulfillView,
OrderTrackingView,
ProductListView,
ProductDetailView,
ProductUpdateView,
ProductCreateView,
ProductDeleteView,
ProductPhotoCreateView,
ProductPhotoDeleteView,
CustomerListView,
CustomerDetailView,
CustomerUpdateView
)
logger = logging.getLogger(__name__)
class OrderCancelViewTests(TestCase):
fixtures = [
'accounts.json',
'coupons.json',
'products.json',
'orders.json'
]
@classmethod
def setUpTestData(cls):
cls.admin_user = User.objects.get(pk=1)
def setUp(self):
self.client = Client()
self.client.force_login(self.admin_user)
def test_view_url_exists_at_desired_location(self):
response = self.client.get('/dashboard/orders/1/cancel/')
self.assertEqual(response.status_code, 200)
def test_view_url_accesible_by_name(self):
response = self.client.get(
reverse('dashboard:order-cancel', kwargs={'pk': 1})
)
self.assertEqual(response.status_code, 200)
def test_view_uses_correct_template(self):
response = self.client.get(
reverse('dashboard:order-cancel', kwargs={'pk': 1})
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'dashboard/order_cancel_form.html')

View File

@ -21,9 +21,8 @@ urlpatterns = [
path('orders/', views.OrderListView.as_view(), name='order-list'),
path('orders/<int:pk>/', include([
path('', views.OrderDetailView.as_view(), name='order-detail'),
# path('update/', views.OrderUpdateView.as_view(), name='product-update'),
# path('delete/', views.OrderDeleteView.as_view(), name='product-delete'),
path('fulfill/', views.OrderFulfillView.as_view(), name='order-fulfill'),
path('cancel/', views.OrderCancelView.as_view(), name='order-cancel'),
path('ship/', views.OrderTrackingView.as_view(), name='order-ship'),
])),

View File

@ -6,7 +6,9 @@ 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.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
@ -34,11 +36,24 @@ from core.models import (
Coupon
)
from core import DiscountValueType, VoucherType, OrderStatus, ShippingMethodType
from .forms import OrderLineFulfillForm, OrderLineFormset, OrderTrackingFormset, CouponForm, ProductPhotoForm
from core import (
DiscountValueType,
VoucherType,
OrderStatus,
ShippingMethodType
)
from .forms import (
OrderLineFulfillForm,
OrderLineFormset,
OrderCancelForm,
OrderTrackingFormset,
CouponForm,
ProductPhotoForm
)
logger = logging.getLogger(__name__)
class DashboardHomeView(LoginRequiredMixin, TemplateView):
template_name = 'dashboard/dashboard_detail.html'
@ -56,6 +71,7 @@ class DashboardHomeView(LoginRequiredMixin, TemplateView):
).aggregate(total=Sum('total_net_amount'))['total']
return context
class DashboardConfigView(TemplateView):
template_name = 'dashboard/config.html'
@ -68,40 +84,42 @@ class DashboardConfigView(TemplateView):
return context
class ShippingMethodCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = ShippingMethod
template_name = 'dashboard/shipmeth_create_form.html'
fields = '__all__'
success_message = '%(name)s created.'
class ShippingMethodDetailView(LoginRequiredMixin, DetailView):
model = ShippingMethod
template_name = 'dashboard/shipmeth_detail.html'
class CouponListView(LoginRequiredMixin, ListView):
model = Coupon
template_name = 'dashboard/coupon_list.html'
class CouponCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
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, SuccessMessageMixin, UpdateView):
model = Coupon
template_name = 'dashboard/coupon_form.html'
success_message = '%(name)s saved.'
form_class = CouponForm
class CouponDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = Coupon
template_name = 'dashboard/coupon_confirm_delete.html'
@ -109,7 +127,6 @@ class CouponDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
success_message = 'Coupon deleted.'
class OrderListView(LoginRequiredMixin, ListView):
model = Order
template_name = 'dashboard/order_list.html'
@ -135,6 +152,7 @@ class OrderListView(LoginRequiredMixin, ListView):
return object_list
class OrderDetailView(LoginRequiredMixin, DetailView):
model = Order
template_name = 'dashboard/order_detail.html'
@ -171,6 +189,20 @@ class OrderFulfillView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
def get_success_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
class OrderCancelView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = Order
template_name = "dashboard/order_cancel_form.html"
form_class = OrderCancelForm
success_message = "Order canceled."
initial = {
'status': OrderStatus.CANCELED
}
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"
@ -198,22 +230,26 @@ class ProductListView(LoginRequiredMixin, ListView):
# )
# return object_list
class ProductDetailView(LoginRequiredMixin, DetailView):
model = Product
template_name = 'dashboard/product_detail.html'
class ProductUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = Product
template_name = 'dashboard/product_update_form.html'
fields = '__all__'
success_message = '%(name)s saved.'
class ProductCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Product
template_name = 'dashboard/product_create_form.html'
fields = '__all__'
success_message = '%(name)s created.'
class ProductDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = Product
template_name = 'dashboard/product_confirm_delete.html'
@ -240,6 +276,7 @@ class ProductPhotoCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView
def get_success_url(self):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class ProductPhotoDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = ProductPhoto
pk_url_kwarg = 'photo_pk'
@ -250,10 +287,6 @@ class ProductPhotoDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
class CustomerListView(LoginRequiredMixin, ListView):
model = User
template_name = 'dashboard/customer_list.html'
@ -271,11 +304,13 @@ class CustomerListView(LoginRequiredMixin, ListView):
return object_list
class CustomerDetailView(LoginRequiredMixin, DetailView):
model = User
template_name = 'dashboard/customer_detail.html'
context_object_name = 'customer'
class CustomerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = User
template_name = 'dashboard/customer_form.html'
@ -292,4 +327,3 @@ class CustomerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
def get_success_url(self):
return reverse('dashboard:customer-detail', kwargs={'pk': self.object.pk})

File diff suppressed because one or more lines are too long