More tests - and some blackness.

This commit is contained in:
Víðir Valberg Guðmundsson 2019-03-29 22:06:52 +01:00
parent e5e1443218
commit 4aad051c72
3 changed files with 280 additions and 178 deletions

View file

@ -3,10 +3,10 @@ from decimal import Decimal
register = template.Library() register = template.Library()
@register.filter @register.filter
def currency(value): def currency(value):
try: try:
return "{0:.2f} DKK".format(Decimal(value)) return "{0:.2f} DKK".format(Decimal(value))
except ValueError: except ValueError:
return False return False

View file

@ -231,12 +231,12 @@ class TestOrderDetailView(TestCase):
self.client.force_login(self.user) self.client.force_login(self.user)
OrderProductRelationFactory(order=self.order) OrderProductRelationFactory(order=self.order)
orp = OrderProductRelationFactory(order=self.order) opr = OrderProductRelationFactory(order=self.order)
order = orp.order order = opr.order
data = self.base_form_data data = self.base_form_data
data["remove_product"] = orp.pk data["remove_product"] = opr.pk
response = self.client.post(self.path, data=data) response = self.client.post(self.path, data=data)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
@ -248,12 +248,12 @@ class TestOrderDetailView(TestCase):
def test_remove_last_product_cancels_order(self): def test_remove_last_product_cancels_order(self):
self.client.force_login(self.user) self.client.force_login(self.user)
orp = OrderProductRelationFactory(order=self.order) opr = OrderProductRelationFactory(order=self.order)
order = orp.order order = opr.order
data = self.base_form_data data = self.base_form_data
data["remove_product"] = orp.pk data["remove_product"] = opr.pk
response = self.client.post(self.path, data=data) response = self.client.post(self.path, data=data)
self.assertEquals(response.status_code, 302) self.assertEquals(response.status_code, 302)
@ -266,11 +266,11 @@ class TestOrderDetailView(TestCase):
def test_cancel_order(self): def test_cancel_order(self):
self.client.force_login(self.user) self.client.force_login(self.user)
orp = OrderProductRelationFactory(order=self.order) opr = OrderProductRelationFactory(order=self.order)
order = orp.order order = opr.order
data = self.base_form_data data = self.base_form_data
data["cancel_order"] = None data["cancel_order"] = ""
response = self.client.post(self.path, data=data) response = self.client.post(self.path, data=data)
self.assertEquals(response.status_code, 302) self.assertEquals(response.status_code, 302)
@ -280,6 +280,69 @@ class TestOrderDetailView(TestCase):
self.assertTrue(order.cancelled) self.assertTrue(order.cancelled)
def test_incrementing_product_quantity(self):
self.client.force_login(self.user)
opr = OrderProductRelationFactory(order=self.order)
opr.product.stock_amount = 100
opr.product.save()
data = self.base_form_data
data["update_order"] = ""
data["form-0-id"] = opr.pk
data["form-0-quantity"] = 11
response = self.client.post(self.path, data=data)
opr.refresh_from_db()
self.assertEquals(response.status_code, 200)
self.assertEquals(opr.quantity, 11)
def test_incrementing_product_quantity_beyond_stock_fails(self):
self.client.force_login(self.user)
opr = OrderProductRelationFactory(order=self.order)
opr.product.stock_amount = 10
opr.product.save()
data = self.base_form_data
data["update_order"] = ""
data["form-0-id"] = opr.pk
data["form-0-quantity"] = 11
response = self.client.post(self.path, data=data)
self.assertEquals(response.status_code, 200)
self.assertIn("quantity", response.context["order_product_formset"].errors[0])
def test_terms_have_to_be_accepted(self):
self.client.force_login(self.user)
opr = OrderProductRelationFactory(order=self.order)
data = self.base_form_data
data["form-0-id"] = opr.pk
data["form-0-quantity"] = 11
data["payment_method"] = "bank_transfer"
response = self.client.post(self.path, data=data)
self.assertEquals(response.status_code, 200)
def test_accepted_terms_and_chosen_payment_method(self):
self.client.force_login(self.user)
opr = OrderProductRelationFactory(order=self.order)
data = self.base_form_data
data["form-0-id"] = opr.pk
data["form-0-quantity"] = 11
data["payment_method"] = "bank_transfer"
data["accept_terms"] = True
response = self.client.post(self.path, data=data)
self.assertEquals(response.status_code, 302)
self.assertRedirects(
response, reverse("shop:bank_transfer", kwargs={"pk": self.order.id})
)
class TestOrderListView(TestCase): class TestOrderListView(TestCase):
def test_order_list_view_as_logged_in(self): def test_order_list_view_as_logged_in(self):

View file

@ -9,19 +9,14 @@ from django.http import (
HttpResponse, HttpResponse,
HttpResponseRedirect, HttpResponseRedirect,
HttpResponseBadRequest, HttpResponseBadRequest,
Http404 Http404,
) )
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import ( from django.views.generic import View, ListView, DetailView, FormView
View,
ListView,
DetailView,
FormView,
)
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
@ -38,7 +33,7 @@ from vendor.coinify.coinify_callback import CoinifyCallback
from .coinify import ( from .coinify import (
create_coinify_invoice, create_coinify_invoice,
save_coinify_callback, save_coinify_callback,
process_coinify_invoice_json process_coinify_invoice_json,
) )
from .epay import calculate_epay_hash, validate_epay_callback from .epay import calculate_epay_hash, validate_epay_callback
from .forms import OrderProductRelationFormSet, OrderProductRelationForm from .forms import OrderProductRelationFormSet, OrderProductRelationForm
@ -53,7 +48,7 @@ class EnsureCreditNoteHasPDFMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not self.get_object().pdf: if not self.get_object().pdf:
messages.error(request, "This creditnote has no PDF yet!") messages.error(request, "This creditnote has no PDF yet!")
return HttpResponseRedirect(reverse_lazy('shop:creditnote_list')) return HttpResponseRedirect(reverse_lazy("shop:creditnote_list"))
return super(EnsureCreditNoteHasPDFMixin, self).dispatch( return super(EnsureCreditNoteHasPDFMixin, self).dispatch(
request, *args, **kwargs request, *args, **kwargs
@ -83,9 +78,7 @@ class EnsureUserOwnsOrderMixin(SingleObjectMixin):
if self.get_object().user != request.user: if self.get_object().user != request.user:
raise Http404("Order not found") raise Http404("Order not found")
return super(EnsureUserOwnsOrderMixin, self).dispatch( return super(EnsureUserOwnsOrderMixin, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
class EnsureUnpaidOrderMixin(SingleObjectMixin): class EnsureUnpaidOrderMixin(SingleObjectMixin):
@ -95,12 +88,10 @@ class EnsureUnpaidOrderMixin(SingleObjectMixin):
if self.get_object().paid: if self.get_object().paid:
messages.error(request, "This order is already paid for!") messages.error(request, "This order is already paid for!")
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk}) reverse_lazy("shop:order_detail", kwargs={"pk": self.get_object().pk})
) )
return super(EnsureUnpaidOrderMixin, self).dispatch( return super(EnsureUnpaidOrderMixin, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
class EnsurePaidOrderMixin(SingleObjectMixin): class EnsurePaidOrderMixin(SingleObjectMixin):
@ -110,12 +101,10 @@ class EnsurePaidOrderMixin(SingleObjectMixin):
if not self.get_object().paid: if not self.get_object().paid:
messages.error(request, "This order is not paid for!") messages.error(request, "This order is not paid for!")
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk}) reverse_lazy("shop:order_detail", kwargs={"pk": self.get_object().pk})
) )
return super(EnsurePaidOrderMixin, self).dispatch( return super(EnsurePaidOrderMixin, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
class EnsureClosedOrderMixin(SingleObjectMixin): class EnsureClosedOrderMixin(SingleObjectMixin):
@ -123,14 +112,12 @@ class EnsureClosedOrderMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if self.get_object().open is not None: if self.get_object().open is not None:
messages.error(request, 'This order is still open!') messages.error(request, "This order is still open!")
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk}) reverse_lazy("shop:order_detail", kwargs={"pk": self.get_object().pk})
) )
return super(EnsureClosedOrderMixin, self).dispatch( return super(EnsureClosedOrderMixin, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
class EnsureOrderHasProductsMixin(SingleObjectMixin): class EnsureOrderHasProductsMixin(SingleObjectMixin):
@ -138,8 +125,8 @@ class EnsureOrderHasProductsMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not self.get_object().products.count() > 0: if not self.get_object().products.count() > 0:
messages.error(request, 'This order has no products!') messages.error(request, "This order has no products!")
return HttpResponseRedirect(reverse_lazy('shop:index')) return HttpResponseRedirect(reverse_lazy("shop:index"))
return super(EnsureOrderHasProductsMixin, self).dispatch( return super(EnsureOrderHasProductsMixin, self).dispatch(
request, *args, **kwargs request, *args, **kwargs
@ -152,10 +139,9 @@ class EnsureOrderIsNotCancelledMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if self.get_object().cancelled: if self.get_object().cancelled:
messages.error( messages.error(
request, request, "Order #{} is cancelled!".format(self.get_object().id)
'Order #{} is cancelled!'.format(self.get_object().id)
) )
return HttpResponseRedirect(reverse_lazy('shop:index')) return HttpResponseRedirect(reverse_lazy("shop:index"))
return super(EnsureOrderIsNotCancelledMixin, self).dispatch( return super(EnsureOrderIsNotCancelledMixin, self).dispatch(
request, *args, **kwargs request, *args, **kwargs
@ -169,7 +155,7 @@ class EnsureOrderHasInvoicePDFMixin(SingleObjectMixin):
if not self.get_object().invoice.pdf: if not self.get_object().invoice.pdf:
messages.error(request, "This order has no invoice yet!") messages.error(request, "This order has no invoice yet!")
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk}) reverse_lazy("shop:order_detail", kwargs={"pk": self.get_object().pk})
) )
return super(EnsureOrderHasInvoicePDFMixin, self).dispatch( return super(EnsureOrderHasInvoicePDFMixin, self).dispatch(
@ -181,17 +167,17 @@ class EnsureOrderHasInvoicePDFMixin(SingleObjectMixin):
class ShopIndexView(ListView): class ShopIndexView(ListView):
model = Product model = Product
template_name = "shop_index.html" template_name = "shop_index.html"
context_object_name = 'products' context_object_name = "products"
def get_queryset(self): def get_queryset(self):
queryset = super(ShopIndexView, self).get_queryset() queryset = super(ShopIndexView, self).get_queryset()
return queryset.available().order_by('category__name', 'price', 'name') return queryset.available().order_by("category__name", "price", "name")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ShopIndexView, self).get_context_data(**kwargs) context = super(ShopIndexView, self).get_context_data(**kwargs)
if 'category' in self.request.GET: if "category" in self.request.GET:
category = self.request.GET.get('category') category = self.request.GET.get("category")
# is this a public category # is this a public category
try: try:
@ -202,41 +188,39 @@ class ShopIndexView(ListView):
raise Http404("Category not found") raise Http404("Category not found")
# filter products by the chosen category # filter products by the chosen category
context['products'] = context['products'].filter( context["products"] = context["products"].filter(category__slug=category)
category__slug=category context["current_category"] = categoryobj
) context["categories"] = ProductCategory.objects.annotate(
context['current_category'] = categoryobj num_products=Count("products")
context['categories'] = ProductCategory.objects.annotate(
num_products=Count('products')
).filter( ).filter(
num_products__gt=0, num_products__gt=0,
public=True, public=True,
products__available_in__contains=timezone.now() products__available_in__contains=timezone.now(),
) )
return context return context
class ProductDetailView(FormView, DetailView): class ProductDetailView(FormView, DetailView):
model = Product model = Product
template_name = 'product_detail.html' template_name = "product_detail.html"
form_class = OrderProductRelationForm form_class = OrderProductRelationForm
context_object_name = 'product' context_object_name = "product"
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
if hasattr(self, 'opr'): if hasattr(self, "opr"):
kwargs['instance'] = self.opr kwargs["instance"] = self.opr
return kwargs return kwargs
def get_initial(self): def get_initial(self):
if hasattr(self, 'opr'): if hasattr(self, "opr"):
return {'quantity': self.opr.quantity} return {"quantity": self.opr.quantity}
return None return None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
# If the OrderProductRelation already exists it has a primary key in the database # If the OrderProductRelation already exists it has a primary key in the database
if self.request.user.is_authenticated and self.opr.pk: if self.request.user.is_authenticated and self.opr.pk:
kwargs['already_in_order'] = True kwargs["already_in_order"] = True
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@ -252,32 +236,28 @@ class ProductDetailView(FormView, DetailView):
self.opr = OrderProductRelation.objects.get( self.opr = OrderProductRelation.objects.get(
order__user=self.request.user, order__user=self.request.user,
order__open__isnull=False, order__open__isnull=False,
product=self.object product=self.object,
) )
except OrderProductRelation.DoesNotExist: except OrderProductRelation.DoesNotExist:
self.opr = OrderProductRelation( self.opr = OrderProductRelation(product=self.get_object(), quantity=1)
product=self.get_object(),
quantity=1,
)
return super(ProductDetailView, self).dispatch( return super(ProductDetailView, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
def form_valid(self, form): def form_valid(self, form):
opr = form.save(commit=False) opr = form.save(commit=False)
if not opr.pk: if not opr.pk:
opr.order, _ = Order.objects.get_or_create(user=self.request.user, open=True, cancelled=False) opr.order, _ = Order.objects.get_or_create(
user=self.request.user, open=True, cancelled=False
)
opr.save() opr.save()
messages.info( messages.info(
self.request, self.request,
'{}x {} has been added to your order.'.format( "{}x {} has been added to your order.".format(
opr.quantity, opr.quantity, opr.product.name
opr.product.name ),
)
) )
# done # done
@ -292,22 +272,28 @@ class ProductDetailView(FormView, DetailView):
class OrderListView(LoginRequiredMixin, ListView): class OrderListView(LoginRequiredMixin, ListView):
model = Order model = Order
template_name = "shop/order_list.html" template_name = "shop/order_list.html"
context_object_name = 'orders' context_object_name = "orders"
def get_queryset(self): def get_queryset(self):
queryset = super(OrderListView, self).get_queryset() queryset = super(OrderListView, self).get_queryset()
return queryset.filter(user=self.request.user).not_cancelled() return queryset.filter(user=self.request.user).not_cancelled()
class OrderDetailView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureOrderHasProductsMixin, EnsureOrderIsNotCancelledMixin, DetailView): class OrderDetailView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsureOrderHasProductsMixin,
EnsureOrderIsNotCancelledMixin,
DetailView,
):
model = Order model = Order
template_name = 'shop/order_detail.html' template_name = "shop/order_detail.html"
context_object_name = 'order' context_object_name = "order"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
if 'order_product_formset' not in kwargs: if "order_product_formset" not in kwargs:
kwargs['order_product_formset'] = OrderProductRelationFormSet( kwargs["order_product_formset"] = OrderProductRelationFormSet(
queryset=OrderProductRelation.objects.filter(order=self.get_object()), queryset=OrderProductRelation.objects.filter(order=self.get_object())
) )
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@ -317,19 +303,19 @@ class OrderDetailView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureOrderH
order = self.object order = self.object
# First check if the user is removing a product from the order. # First check if the user is removing a product from the order.
product_remove = request.POST.get('remove_product') product_remove = request.POST.get("remove_product")
if product_remove: if product_remove:
order.orderproductrelation_set.filter(pk=product_remove).delete() order.orderproductrelation_set.filter(pk=product_remove).delete()
if not order.products.count() > 0: if not order.products.count() > 0:
order.mark_as_cancelled() order.mark_as_cancelled()
messages.info(request, 'Order cancelled!') messages.info(request, "Order cancelled!")
return HttpResponseRedirect(reverse_lazy('shop:index')) return HttpResponseRedirect(reverse_lazy("shop:index"))
# Then see if the user is cancelling the order. # Then see if the user is cancelling the order.
elif 'cancel_order' in request.POST: elif "cancel_order" in request.POST:
order.mark_as_cancelled() order.mark_as_cancelled()
messages.info(request, 'Order cancelled!') messages.info(request, "Order cancelled!")
return HttpResponseRedirect(reverse_lazy('shop:index')) return HttpResponseRedirect(reverse_lazy("shop:index"))
# The user is not removing products or cancelling the order, # The user is not removing products or cancelling the order,
# so from now on we do stuff that require us to check stock. # so from now on we do stuff that require us to check stock.
@ -337,62 +323,58 @@ class OrderDetailView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureOrderH
# which product is not in stock if that is the case. # which product is not in stock if that is the case.
else: else:
formset = OrderProductRelationFormSet( formset = OrderProductRelationFormSet(
request.POST, request.POST, queryset=OrderProductRelation.objects.filter(order=order)
queryset=OrderProductRelation.objects.filter(order=order),
) )
# If the formset is not valid it means that we cannot fulfill the order, so return and inform the user. # If the formset is not valid it means that we cannot fulfill the order, so return and inform the user.
if not formset.is_valid(): if not formset.is_valid():
messages.error( messages.error(
request, request,
"Some of the products you are ordering are out of stock. Review the order and try again." "Some of the products you are ordering are out of stock. Review the order and try again.",
) )
return self.render_to_response( return self.render_to_response(
self.get_context_data(order_product_formset=formset) self.get_context_data(order_product_formset=formset)
) )
# No stock issues, proceed to check if the user is updating the order. # No stock issues, proceed to check if the user is updating the order.
if 'update_order' in request.POST: if "update_order" in request.POST:
# We have already made sure the formset is valid, so just save it to update quantities. # We have already made sure the formset is valid, so just save it to update quantities.
formset.save() formset.save()
order.customer_comment = request.POST.get('customer_comment') or '' order.customer_comment = request.POST.get("customer_comment") or ""
order.invoice_address = request.POST.get('invoice_address') or '' order.invoice_address = request.POST.get("invoice_address") or ""
order.save() order.save()
# Then at last see if the user is paying for the order. # Then at last see if the user is paying for the order.
payment_method = request.POST.get('payment_method') payment_method = request.POST.get("payment_method")
if payment_method in order.PAYMENT_METHODS: if payment_method in order.PAYMENT_METHODS:
if not request.POST.get('accept_terms'): if not request.POST.get("accept_terms"):
messages.error(request, "You need to accept the general terms and conditions before you can continue!") messages.error(
return HttpResponseRedirect( request,
reverse_lazy('shop:order_detail', kwargs={'pk': order.pk}) "You need to accept the general terms and conditions before you can continue!",
)
return self.render_to_response(
self.get_context_data(order_product_formset=formset)
) )
# Set payment method and mark the order as closed # Set payment method and mark the order as closed
order.payment_method = payment_method order.payment_method = payment_method
order.open = None order.open = None
order.customer_comment = request.POST.get('customer_comment') or '' order.customer_comment = request.POST.get("customer_comment") or ""
order.invoice_address = request.POST.get('invoice_address') or '' order.invoice_address = request.POST.get("invoice_address") or ""
order.save() order.save()
reverses = { reverses = {
Order.CREDIT_CARD: reverse_lazy( Order.CREDIT_CARD: reverse_lazy(
'shop:epay_form', "shop:epay_form", kwargs={"pk": order.id}
kwargs={'pk': order.id}
), ),
Order.BLOCKCHAIN: reverse_lazy( Order.BLOCKCHAIN: reverse_lazy(
'shop:coinify_pay', "shop:coinify_pay", kwargs={"pk": order.id}
kwargs={'pk': order.id}
), ),
Order.BANK_TRANSFER: reverse_lazy( Order.BANK_TRANSFER: reverse_lazy(
'shop:bank_transfer', "shop:bank_transfer", kwargs={"pk": order.id}
kwargs={'pk': order.id}
), ),
Order.CASH: reverse_lazy( Order.CASH: reverse_lazy("shop:cash", kwargs={"pk": order.id}),
'shop:cash',
kwargs={'pk': order.id}
)
} }
return HttpResponseRedirect(reverses[payment_method]) return HttpResponseRedirect(reverses[payment_method])
@ -400,12 +382,21 @@ class OrderDetailView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureOrderH
return super(OrderDetailView, self).get(request, *args, **kwargs) return super(OrderDetailView, self).get(request, *args, **kwargs)
class DownloadInvoiceView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsurePaidOrderMixin, EnsureOrderHasInvoicePDFMixin, SingleObjectMixin, View): class DownloadInvoiceView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsurePaidOrderMixin,
EnsureOrderHasInvoicePDFMixin,
SingleObjectMixin,
View,
):
model = Order model = Order
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
response = HttpResponse(content_type='application/pdf') response = HttpResponse(content_type="application/pdf")
response['Content-Disposition'] = 'attachment; filename="%s"' % self.get_object().invoice.filename response["Content-Disposition"] = (
'attachment; filename="%s"' % self.get_object().invoice.filename
)
response.write(self.get_object().invoice.pdf.read()) response.write(self.get_object().invoice.pdf.read())
return response return response
@ -413,19 +404,27 @@ class DownloadInvoiceView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsurePa
class CreditNoteListView(LoginRequiredMixin, ListView): class CreditNoteListView(LoginRequiredMixin, ListView):
model = CreditNote model = CreditNote
template_name = "shop/creditnote_list.html" template_name = "shop/creditnote_list.html"
context_object_name = 'creditnotes' context_object_name = "creditnotes"
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
return queryset.filter(user=self.request.user) return queryset.filter(user=self.request.user)
class DownloadCreditNoteView(LoginRequiredMixin, EnsureUserOwnsCreditNoteMixin, EnsureCreditNoteHasPDFMixin, SingleObjectMixin, View): class DownloadCreditNoteView(
LoginRequiredMixin,
EnsureUserOwnsCreditNoteMixin,
EnsureCreditNoteHasPDFMixin,
SingleObjectMixin,
View,
):
model = CreditNote model = CreditNote
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
response = HttpResponse(content_type='application/pdf') response = HttpResponse(content_type="application/pdf")
response['Content-Disposition'] = 'attachment; filename="%s"' % self.get_object().filename response["Content-Disposition"] = (
'attachment; filename="%s"' % self.get_object().filename
)
response.write(self.get_object().pdf.read()) response.write(self.get_object().pdf.read())
return response return response
@ -436,31 +435,38 @@ class OrderMarkAsPaidView(LoginRequiredMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not request.user.is_staff: if not request.user.is_staff:
messages.error(request, 'You do not have permissions to do that.') messages.error(request, "You do not have permissions to do that.")
return HttpResponseRedirect(reverse_lazy('shop:index')) return HttpResponseRedirect(reverse_lazy("shop:index"))
else: else:
messages.success(request, 'The order has been marked as paid.') messages.success(request, "The order has been marked as paid.")
order = self.get_object() order = self.get_object()
order.mark_as_paid() order.mark_as_paid()
return HttpResponseRedirect(request.META.get('HTTP_REFERER')) return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
# Epay views # Epay views
class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView): class EpayFormView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsureUnpaidOrderMixin,
EnsureClosedOrderMixin,
EnsureOrderHasProductsMixin,
DetailView,
):
model = Order model = Order
template_name = 'epay_form.html' template_name = "epay_form.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
order = self.get_object() order = self.get_object()
context = super(EpayFormView, self).get_context_data(**kwargs) context = super(EpayFormView, self).get_context_data(**kwargs)
context['merchant_number'] = settings.EPAY_MERCHANT_NUMBER context["merchant_number"] = settings.EPAY_MERCHANT_NUMBER
context['description'] = order.description context["description"] = order.description
context['amount'] = order.total * 100 context["amount"] = order.total * 100
context['order_id'] = order.pk context["order_id"] = order.pk
context['accept_url'] = order.get_epay_accept_url(self.request) context["accept_url"] = order.get_epay_accept_url(self.request)
context['cancel_url'] = order.get_cancel_url(self.request) context["cancel_url"] = order.get_cancel_url(self.request)
context['callback_url'] = order.get_epay_callback_url(self.request) context["callback_url"] = order.get_epay_callback_url(self.request)
context['epay_hash'] = calculate_epay_hash(order, self.request) context["epay_hash"] = calculate_epay_hash(order, self.request)
return context return context
@ -468,21 +474,19 @@ class EpayCallbackView(SingleObjectMixin, View):
model = Order model = Order
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
callback = EpayCallback.objects.create( callback = EpayCallback.objects.create(payload=request.GET)
payload=request.GET
)
if 'orderid' in request.GET: if "orderid" in request.GET:
query = OrderedDict( query = OrderedDict(
[tuple(x.split('=')) for x in request.META['QUERY_STRING'].split('&')] [tuple(x.split("=")) for x in request.META["QUERY_STRING"].split("&")]
) )
order = get_object_or_404(Order, pk=query.get('orderid')) order = get_object_or_404(Order, pk=query.get("orderid"))
if order.pk != self.get_object().pk: if order.pk != self.get_object().pk:
logger.error("bad epay callback, orders do not match!") logger.error("bad epay callback, orders do not match!")
return HttpResponse(status=400) return HttpResponse(status=400)
if validate_epay_callback(query): if validate_epay_callback(query):
callback.md5valid=True callback.md5valid = True
callback.save() callback.save()
else: else:
logger.error("bad epay callback!") logger.error("bad epay callback!")
@ -490,15 +494,13 @@ class EpayCallbackView(SingleObjectMixin, View):
if order.paid: if order.paid:
# this order is already paid, perhaps we are seeing a double callback? # this order is already paid, perhaps we are seeing a double callback?
return HttpResponse('OK') return HttpResponse("OK")
# epay callback is valid - has the order been paid in full? # epay callback is valid - has the order been paid in full?
if int(query['amount']) == order.total * 100: if int(query["amount"]) == order.total * 100:
# create an EpayPayment object linking the callback to the order # create an EpayPayment object linking the callback to the order
EpayPayment.objects.create( EpayPayment.objects.create(
order=order, order=order, callback=callback, txnid=query.get("txnid")
callback=callback,
txnid=query.get('txnid'),
) )
# and mark order as paid (this will create tickets) # and mark order as paid (this will create tickets)
order.mark_as_paid(request) order.mark_as_paid(request)
@ -507,53 +509,76 @@ class EpayCallbackView(SingleObjectMixin, View):
else: else:
return HttpResponse(status=400) return HttpResponse(status=400)
return HttpResponse('OK') return HttpResponse("OK")
class EpayThanksView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureClosedOrderMixin, DetailView): class EpayThanksView(
LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureClosedOrderMixin, DetailView
):
model = Order model = Order
template_name = 'epay_thanks.html' template_name = "epay_thanks.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.GET: if request.GET:
# epay redirects the user back to our accepturl with a long # epay redirects the user back to our accepturl with a long
# and ugly querystring, redirect user to the clean url # and ugly querystring, redirect user to the clean url
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('shop:epay_thanks', kwargs={'pk': self.get_object().pk}) reverse("shop:epay_thanks", kwargs={"pk": self.get_object().pk})
) )
return super(EpayThanksView, self).dispatch( return super(EpayThanksView, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
# Bank Transfer view # Bank Transfer view
class BankTransferView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureOrderHasProductsMixin, DetailView):
class BankTransferView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsureUnpaidOrderMixin,
EnsureOrderHasProductsMixin,
DetailView,
):
model = Order model = Order
template_name = 'bank_transfer.html' template_name = "bank_transfer.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(BankTransferView, self).get_context_data(**kwargs) context = super(BankTransferView, self).get_context_data(**kwargs)
context['iban'] = settings.BANKACCOUNT_IBAN context["iban"] = settings.BANKACCOUNT_IBAN
context['swiftbic'] = settings.BANKACCOUNT_SWIFTBIC context["swiftbic"] = settings.BANKACCOUNT_SWIFTBIC
context['orderid'] = self.get_object().pk context["orderid"] = self.get_object().pk
context['regno'] = settings.BANKACCOUNT_REG context["regno"] = settings.BANKACCOUNT_REG
context['accountno'] = settings.BANKACCOUNT_ACCOUNT context["accountno"] = settings.BANKACCOUNT_ACCOUNT
context['total'] = self.get_object().total context["total"] = self.get_object().total
return context return context
# Cash payment view # Cash payment view
class CashView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureOrderHasProductsMixin, DetailView):
class CashView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsureUnpaidOrderMixin,
EnsureOrderHasProductsMixin,
DetailView,
):
model = Order model = Order
template_name = 'cash.html' template_name = "cash.html"
# Coinify views # Coinify views
class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, SingleObjectMixin, RedirectView):
class CoinifyRedirectView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsureUnpaidOrderMixin,
EnsureClosedOrderMixin,
EnsureOrderHasProductsMixin,
SingleObjectMixin,
RedirectView,
):
model = Order model = Order
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@ -563,17 +588,20 @@ class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUn
if not order.coinifyapiinvoice: if not order.coinifyapiinvoice:
coinifyinvoice = create_coinify_invoice(order, request) coinifyinvoice = create_coinify_invoice(order, request)
if not coinifyinvoice: if not coinifyinvoice:
messages.error(request, "There was a problem with the payment provider. Please try again later") messages.error(
request,
"There was a problem with the payment provider. Please try again later",
)
return HttpResponseRedirect( return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk}) reverse_lazy(
"shop:order_detail", kwargs={"pk": self.get_object().pk}
)
) )
return super(CoinifyRedirectView, self).dispatch( return super(CoinifyRedirectView, self).dispatch(request, *args, **kwargs)
request, *args, **kwargs
)
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return self.get_object().coinifyapiinvoice.invoicejson['payment_url'] return self.get_object().coinifyapiinvoice.invoicejson["payment_url"]
class CoinifyCallbackView(SingleObjectMixin, View): class CoinifyCallbackView(SingleObjectMixin, View):
@ -590,34 +618,45 @@ class CoinifyCallbackView(SingleObjectMixin, View):
# do we have a json body? # do we have a json body?
if not callbackobject.payload: if not callbackobject.payload:
# no, return an error # no, return an error
logger.error("unable to parse JSON body in callback for order %s" % callbackobject.order.id) logger.error(
return HttpResponseBadRequest('unable to parse json') "unable to parse JSON body in callback for order %s"
% callbackobject.order.id
)
return HttpResponseBadRequest("unable to parse json")
# initiate SDK # initiate SDK
sdk = CoinifyCallback(settings.COINIFY_IPN_SECRET.encode('utf-8')) sdk = CoinifyCallback(settings.COINIFY_IPN_SECRET.encode("utf-8"))
# attemt to validate the callbackc # attemt to validate the callbackc
if sdk.validate_callback(request.body, request.META['HTTP_X_COINIFY_CALLBACK_SIGNATURE']): if sdk.validate_callback(
request.body, request.META["HTTP_X_COINIFY_CALLBACK_SIGNATURE"]
):
# mark callback as valid in db # mark callback as valid in db
callbackobject.valid = True callbackobject.valid = True
callbackobject.save() callbackobject.save()
else: else:
logger.error("invalid coinify callback detected") logger.error("invalid coinify callback detected")
return HttpResponseBadRequest('something is fucky') return HttpResponseBadRequest("something is fucky")
if callbackobject.payload['event'] == 'invoice_state_change' or callbackobject.payload['event'] == 'invoice_manual_resend': if (
callbackobject.payload["event"] == "invoice_state_change"
or callbackobject.payload["event"] == "invoice_manual_resend"
):
process_coinify_invoice_json( process_coinify_invoice_json(
invoicejson=callbackobject.payload['data'], invoicejson=callbackobject.payload["data"],
order=self.get_object(), order=self.get_object(),
request=request, request=request,
) )
return HttpResponse('OK') return HttpResponse("OK")
else: else:
logger.error("unsupported callback event %s" % callbackobject.payload['event']) logger.error(
return HttpResponseBadRequest('unsupported event') "unsupported callback event %s" % callbackobject.payload["event"]
)
return HttpResponseBadRequest("unsupported event")
class CoinifyThanksView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureClosedOrderMixin, DetailView): class CoinifyThanksView(
LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureClosedOrderMixin, DetailView
):
model = Order model = Order
template_name = 'coinify_thanks.html' template_name = "coinify_thanks.html"