Skip to content

Commit

Permalink
Merge pull request #657 from awesto/cancel-order-with-refund
Browse files Browse the repository at this point in the history
Cancel order with refund
  • Loading branch information
jrief committed Oct 23, 2017
2 parents 7c5a29a + bbac86e commit ca15f89
Show file tree
Hide file tree
Showing 26 changed files with 316 additions and 125 deletions.
18 changes: 14 additions & 4 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ Changelog for django-SHOP
======

* Do not render buttons and links related to the watch-list, when it is not available.
* Use Sekizai's internal ``{% with_data ... %}`` to render Sekizai blocks ``ng-requires`` and
``ng-config`` rather than using the deprecated postprocessors ``djng.sekizai_processors.module_list``
and ``djng.sekizai_processors.module_config``. Adopt your templates accordingly as explained
in :ref:`reference/client-framework`
* Use Sekizai's internal templatetags ``{% with_data ... %}`` and ``{% with_data %}`` to render Sekizai
blocks ``ng-requires`` and ``ng-config`` rather than using the deprecated postprocessors
``djng.sekizai_processors.module_list`` and ``djng.sekizai_processors.module_config``. Adopt your
templates accordingly as explained in :ref:`reference/client-framework`.
* Rename ``PayInAdvanceWorkflowMixin`` to ``ManualPaymentWorkflowMixin``, since its purpose is to
handle all incoming/outgoing payments manually.
* Move ``LeftExtensionPlugin`` and ``RightExtensionPlugin`` into module ``shop/cascade/extensions``
and allow them to be used on the ``ShopOrderViewsPlugin`` as well.
* ``ShopOrderAddendumFormPlugin`` can optionally render historical annotations for the given order.
* Added hook methods ``cancelable()`` and ``refund_payment()`` to ``BaseOrder`` to allow
a better order cancelling interface.
* Paid but unshipped orders, now can be refunded. Possible be refactoring class
``CancelOrderWorkflowMixin``, which handles payment refunds.
* Add Order status to Order Detail View, so that the customer immediately sees what's going on.


0.11.1
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/migrations/commodity/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
},
bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
bases=(shop.payment.defaults.ManualPaymentWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
),
migrations.CreateModel(
name='OrderItem',
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/migrations/i18n_commodity/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
},
bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
bases=(shop.payment.defaults.ManualPaymentWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
),
migrations.CreateModel(
name='OrderItem',
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/migrations/i18n_polymorphic/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
},
bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.delivery.PartialDeliveryWorkflowMixin, models.Model),
bases=(shop.payment.defaults.ManualPaymentWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.delivery.PartialDeliveryWorkflowMixin, models.Model),
),
migrations.CreateModel(
name='OrderItem',
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/migrations/i18n_smartcard/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
},
bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
bases=(shop.payment.defaults.ManualPaymentWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
),
migrations.CreateModel(
name='OrderItem',
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/migrations/polymorphic/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
},
bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.delivery.PartialDeliveryWorkflowMixin, models.Model),
bases=(shop.payment.defaults.ManualPaymentWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.delivery.PartialDeliveryWorkflowMixin, models.Model),
),
migrations.CreateModel(
name='OrderItem',
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/migrations/smartcard/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
},
bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
bases=(shop.payment.defaults.ManualPaymentWorkflowMixin, shop.payment.defaults.CancelOrderWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, shop.shipping.defaults.CommissionGoodsWorkflowMixin, models.Model),
),
migrations.CreateModel(
name='OrderItem',
Expand Down
2 changes: 1 addition & 1 deletion example/myshop/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@
SHOP_EDITCART_NG_MODEL_OPTIONS = "{updateOn: 'default blur', debounce: {'default': 2500, 'blur': 0}}"

SHOP_ORDER_WORKFLOWS = [
'shop.payment.defaults.PayInAdvanceWorkflowMixin',
'shop.payment.defaults.ManualPaymentWorkflowMixin',
'shop.payment.defaults.CancelOrderWorkflowMixin',
'shop_stripe.payment.OrderWorkflowMixin',
]
Expand Down
56 changes: 4 additions & 52 deletions shop/cascade/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,19 @@
from django.template.loader import select_template, get_template
from django.utils.translation import ugettext_lazy as _
from django.utils.html import mark_safe

from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.fields import GlossaryField
from cmsplugin_cascade.plugin_base import TransparentContainer, TransparentWrapper
from cmsplugin_cascade.plugin_base import TransparentWrapper

from shop.conf import app_settings
from shop.models.cart import CartModel
from shop.serializers.cart import CartSerializer
from .extensions import ShopExtendableMixin, LeftRightExtensionMixin
from .plugin_base import ShopPluginBase


class ShopExtendableMixin(object):
@property
def left_extension(self):
result = [cp for cp in self.child_plugin_instances if cp.plugin_type == 'ShopLeftExtension']
if result:
return result[0]

@property
def right_extension(self):
result = [cp for cp in self.child_plugin_instances if cp.plugin_type == 'ShopRightExtension']
if result:
return result[0]


class ShopCartPlugin(TransparentWrapper, ShopPluginBase):
class ShopCartPlugin(LeftRightExtensionMixin, TransparentWrapper, ShopPluginBase):
name = _("Cart")
require_parent = True
parent_classes = ('BootstrapColumnPlugin',)
Expand Down Expand Up @@ -94,40 +82,4 @@ def render(self, context, instance, placeholder):
pass
return self.super(ShopCartPlugin, self).render(context, instance, placeholder)

@classmethod
def get_child_classes(cls, slot, page, instance=None):
child_classes = ['ShopLeftExtension', 'ShopRightExtension', None]
# allow only one left and one right extension
for child in instance.get_children():
child_classes.remove(child.plugin_type)
return child_classes

@classmethod
def get_child_classes(cls, slot, page, instance=None):
child_classes = ['ShopLeftExtension', 'ShopRightExtension', None]
# allow only one left and one right extension
for child in instance.get_children():
child_classes.remove(child.plugin_type)
return child_classes

plugin_pool.register_plugin(ShopCartPlugin)


class ShopLeftExtension(TransparentContainer, ShopPluginBase):
name = _("Left Extension")
require_parent = True
parent_classes = ('ShopCartPlugin',)
allow_children = True
render_template = 'cascade/generic/naked.html'

plugin_pool.register_plugin(ShopLeftExtension)


class ShopRightExtension(TransparentContainer, ShopPluginBase):
name = _("Right Extension")
require_parent = True
parent_classes = ('ShopCartPlugin',)
allow_children = True
render_template = 'cascade/generic/naked.html'

plugin_pool.register_plugin(ShopRightExtension)
5 changes: 1 addition & 4 deletions shop/cascade/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ShopProceedButton(BootstrapButtonMixin, ShopButtonPluginBase):
name = _("Proceed Button")
parent_classes = ('BootstrapColumnPlugin', 'ProcessStepPlugin', 'ValidateSetOfFormsPlugin')
model_mixins = (LinkElementMixin,)
form = ProceedButtonForm
glossary_field_order = ('button_type', 'button_size', 'button_options', 'quick_float',
'icon_align', 'icon_font', 'symbol')
ring_plugin = 'ProceedButtonPlugin'
Expand All @@ -59,10 +60,6 @@ class Media:
'cascade/css/admin/iconplugin.css',)}
js = ['shop/js/admin/proceedbuttonplugin.js']

def get_form(self, request, obj=None, **kwargs):
kwargs.update(form=ProceedButtonForm)
return super(ShopProceedButton, self).get_form(request, obj, **kwargs)

def get_render_template(self, context, instance, placeholder):
template_names = [
'{}/checkout/proceed-button.html'.format(app_settings.APP_LABEL),
Expand Down
58 changes: 58 additions & 0 deletions shop/cascade/extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.utils.translation import ugettext_lazy as _
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.plugin_base import TransparentContainer

from .plugin_base import ShopPluginBase


class ShopExtendableMixin(object):
"""
Add this mixin class to the list of ``model_mixins``, in the plugin class wishing to use extensions.
"""
@property
def left_extension(self):
result = [cp for cp in self.child_plugin_instances if cp.plugin_type == 'ShopLeftExtension']
if result:
return result[0]

@property
def right_extension(self):
result = [cp for cp in self.child_plugin_instances if cp.plugin_type == 'ShopRightExtension']
if result:
return result[0]


class LeftRightExtensionMixin(object):
"""
Plugin classes wishing to use extensions shall inherit from this class.
"""
@classmethod
def get_child_classes(cls, slot, page, instance=None):
child_classes = ['ShopLeftExtension', 'ShopRightExtension', None]
# allow only one left and one right extension
for child in instance.get_children():
child_classes.remove(child.plugin_type)
return child_classes


class ShopLeftExtension(TransparentContainer, ShopPluginBase):
name = _("Left Extension")
require_parent = True
parent_classes = ('ShopCartPlugin', 'ShopOrderViewsPlugin')
allow_children = True
render_template = 'cascade/generic/naked.html'

plugin_pool.register_plugin(ShopLeftExtension)


class ShopRightExtension(TransparentContainer, ShopPluginBase):
name = _("Right Extension")
require_parent = True
parent_classes = ('ShopCartPlugin', 'ShopOrderViewsPlugin')
allow_children = True
render_template = 'cascade/generic/naked.html'

plugin_pool.register_plugin(ShopRightExtension)
86 changes: 63 additions & 23 deletions shop/cascade/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
from django.template import engines
from django.template.loader import select_template
from django.utils.translation import ugettext_lazy as _

from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.bootstrap3.buttons import BootstrapButtonMixin
from cmsplugin_cascade.fields import GlossaryField
from cmsplugin_cascade.plugin_base import TransparentWrapper

from djng.forms import fields, NgModelFormMixin
from djng.styling.bootstrap3.forms import Bootstrap3Form

from shop.conf import app_settings
from .extensions import ShopExtendableMixin, LeftRightExtensionMixin
from .plugin_base import ShopPluginBase


Expand All @@ -26,10 +30,12 @@ def clean(self):
return cleaned_data


class ShopOrderViewsPlugin(ShopPluginBase):
class ShopOrderViewsPlugin(LeftRightExtensionMixin, TransparentWrapper, ShopPluginBase):
name = _("Order Views")
require_parent = True
parent_classes = ('BootstrapColumnPlugin',)
allow_children = True
model_mixins = (ShopExtendableMixin,)
form = ShopOrderViewsForm
cache = False

Expand All @@ -53,7 +59,7 @@ def get_render_template(self, context, instance, placeholder):
plugin_pool.register_plugin(ShopOrderViewsPlugin)


class ReorderButtonForm(ShopOrderViewsForm):
class OrderButtonForm(ShopOrderViewsForm):
button_content = fields.CharField(required=False, label=_("Button Content"),
widget=widgets.TextInput())

Expand All @@ -62,48 +68,79 @@ def __init__(self, raw_data=None, *args, **kwargs):
if instance:
initial = {'button_content': instance.glossary.get('button_content') }
kwargs.update(initial=initial)
super(ReorderButtonForm, self).__init__(raw_data, *args, **kwargs)
super(OrderButtonForm, self).__init__(raw_data, *args, **kwargs)

def clean(self):
cleaned_data = super(ReorderButtonForm, self).clean()
cleaned_data = super(OrderButtonForm, self).clean()
if self.is_valid():
cleaned_data['glossary']['button_content'] = cleaned_data['button_content']
return cleaned_data


class ShopReorderFormPlugin(BootstrapButtonMixin, ShopPluginBase):
name = _("Reorder Button")
parent_classes = ('BootstrapColumnPlugin', 'SimpleWrapperPlugin',)
form = ReorderButtonForm
fields = ('button_content', 'glossary',)
class OrderButtonBase(BootstrapButtonMixin, ShopPluginBase):
parent_classes = ['ShopOrderViewsPlugin']
form = OrderButtonForm
fields = ['button_content', 'glossary']
glossary_field_order = ['button_type', 'button_size', 'button_options', 'quick_float',
'icon_align', 'icon_font', 'symbol']

class Media:
css = {'all': ('cascade/css/admin/bootstrap.min.css', 'cascade/css/admin/bootstrap-theme.min.css',)}

@classmethod
def get_identifier(cls, instance):
return instance.glossary.get('button_content', '')

def render(self, context, instance, placeholder):
context = super(OrderButtonBase, self).render(context, instance, placeholder)
context.update({
'button_label': instance.glossary.get('button_content', '')
})
return context


class ShopReorderButtonPlugin(OrderButtonBase):
name = _("Reorder Button")

def get_render_template(self, context, instance, placeholder):
template_names = [
'{}/order/reorder-form.html'.format(app_settings.APP_LABEL),
'shop/order/reorder-form.html',
'{}/order/reorder-button.html'.format(app_settings.APP_LABEL),
'shop/order/reorder-button.html',
]
return select_template(template_names)

plugin_pool.register_plugin(ShopReorderFormPlugin)
plugin_pool.register_plugin(ShopReorderButtonPlugin)


class ShopCancelOrderButtonPlugin(OrderButtonBase):
name = _("Cancel Order Button")

def get_render_template(self, context, instance, placeholder):
template_names = [
'{}/order/cancel-button.html'.format(app_settings.APP_LABEL),
'shop/order/cancel-button.html',
]
return select_template(template_names)

plugin_pool.register_plugin(ShopCancelOrderButtonPlugin)


class AddendumForm(NgModelFormMixin, Bootstrap3Form):
scope_prefix = 'data'
annotation = fields.CharField(label=_("Supplementary annotation for this Order"), required=False,
widget=widgets.Textarea(attrs={'rows': 4}))
annotation = fields.CharField(
label=_("Supplementary annotation for this Order"),
widget=widgets.Textarea(attrs={'rows': 2}),
)


class ShopOrderAddendumFormPlugin(BootstrapButtonMixin, ShopPluginBase):
class ShopOrderAddendumFormPlugin(OrderButtonBase):
name = _("Order Addendum Form")
parent_classes = ('BootstrapColumnPlugin', 'SimpleWrapperPlugin',)
form = ReorderButtonForm
fields = ('button_content', 'glossary',)

class Media:
css = {'all': ('cascade/css/admin/bootstrap.min.css', 'cascade/css/admin/bootstrap-theme.min.css',)}
show_history = GlossaryField(
widgets.CheckboxInput(),
label=_("Show History"),
initial=True,
help_text=_("Show historical annotations.")
)

def get_render_template(self, context, instance, placeholder):
template_names = [
Expand All @@ -113,8 +150,11 @@ def get_render_template(self, context, instance, placeholder):
return select_template(template_names)

def render(self, context, instance, placeholder):
super(ShopOrderAddendumFormPlugin, self).render(context, instance, placeholder)
context['addenum_form'] = AddendumForm()
context = super(ShopOrderAddendumFormPlugin, self).render(context, instance, placeholder)
context.update({
'addenum_form': AddendumForm(),
'show_history': instance.glossary.get('show_history', True),
})
return context

plugin_pool.register_plugin(ShopOrderAddendumFormPlugin)

0 comments on commit ca15f89

Please sign in to comment.