cleanup in shop/

This commit is contained in:
Stephan Telling 2018-03-04 15:38:40 +01:00
parent 139d2deff9
commit 488767b4f0
11 changed files with 70 additions and 69 deletions

View File

@ -40,6 +40,7 @@ def available_from(product):
return "None" return "None"
available_from.short_description = 'Available from' available_from.short_description = 'Available from'
def available_to(product): def available_to(product):
if product.available_in.upper: if product.available_in.upper:
return product.available_in.upper.strftime("%c") return product.available_in.upper.strftime("%c")

View File

@ -2,6 +2,7 @@ from django.apps import AppConfig
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
class ShopConfig(AppConfig): class ShopConfig(AppConfig):
name = 'shop' name = 'shop'

View File

@ -1,8 +1,9 @@
from vendor.coinify.coinify_api import CoinifyAPI from vendor.coinify.coinify_api import CoinifyAPI
from vendor.coinify.coinify_callback import CoinifyCallback
from .models import CoinifyAPIRequest, CoinifyAPIInvoice, CoinifyAPICallback from .models import CoinifyAPIRequest, CoinifyAPIInvoice, CoinifyAPICallback
from django.conf import settings from django.conf import settings
import json, logging import json
import logging
import requests
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -34,7 +35,7 @@ def save_coinify_callback(request, order):
# now attempt to parse json # now attempt to parse json
try: try:
parsed = json.loads(request.body.decode('utf-8')) parsed = json.loads(request.body.decode('utf-8'))
except Exception as E: except Exception:
parsed = '' parsed = ''
# save this callback to db # save this callback to db
@ -89,8 +90,8 @@ def handle_coinify_api_response(req, order):
if req.response['success']: if req.response['success']:
# save this new coinify invoice to the DB # save this new coinify invoice to the DB
coinifyinvoice = process_coinify_invoice_json( coinifyinvoice = process_coinify_invoice_json(
invoicejson = req.response['data'], invoicejson=req.response['data'],
order = order, order=order,
) )
return coinifyinvoice return coinifyinvoice
else: else:

View File

@ -1,6 +1,3 @@
from django.conf import settings
def current_order(request): def current_order(request):
if request.user.is_authenticated(): if request.user.is_authenticated():
order = None order = None
@ -11,5 +8,3 @@ def current_order(request):
return {'current_order': order} return {'current_order': order}
return {} return {}

View File

@ -1,6 +1,7 @@
import hashlib import hashlib
from django.conf import settings from django.conf import settings
def calculate_epay_hash(order, request): def calculate_epay_hash(order, request):
hashstring = ( hashstring = (
'{merchant_number}{description}11{amount}DKK' '{merchant_number}{description}11{amount}DKK'
@ -10,9 +11,9 @@ def calculate_epay_hash(order, request):
description=order.description, description=order.description,
amount=order.total*100, amount=order.total*100,
order_id=order.pk, order_id=order.pk,
accept_url = order.get_epay_accept_url(request), accept_url=order.get_epay_accept_url(request),
cancel_url = order.get_cancel_url(request), cancel_url=order.get_cancel_url(request),
callback_url = order.get_epay_callback_url(request), callback_url=order.get_epay_callback_url(request),
md5_secret=settings.EPAY_MD5_SECRET, md5_secret=settings.EPAY_MD5_SECRET,
) )
epay_hash = hashlib.md5(hashstring.encode('utf-8')).hexdigest() epay_hash = hashlib.md5(hashstring.encode('utf-8')).hexdigest()
@ -24,6 +25,7 @@ def validate_epay_callback(query):
for key, value in query.items(): for key, value in query.items():
if key != 'hash': if key != 'hash':
hashstring += value hashstring += value
hash = hashlib.md5((hashstring + settings.EPAY_MD5_SECRET).encode('utf-8')).hexdigest() hash = hashlib.md5(
(hashstring + settings.EPAY_MD5_SECRET).encode('utf-8')
).hexdigest()
return hash == query['hash'] return hash == query['hash']

View File

@ -1,5 +1,4 @@
from django import forms from django import forms
from .models import Order
class AddToOrderForm(forms.Form): class AddToOrderForm(forms.Form):

View File

@ -1,5 +1,3 @@
from psycopg2.extras import DateTimeTZRange
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils import timezone from django.utils import timezone

View File

@ -1,8 +1,4 @@
import io
import logging import logging
import hashlib
import base64
import qrcode
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
@ -202,7 +198,7 @@ class Order(CreatedUpdatedModel):
if request: if request:
messages.success(request, "Created %s tickets of type: %s" % (order_product.quantity, order_product.product.ticket_type.name)) messages.success(request, "Created %s tickets of type: %s" % (order_product.quantity, order_product.product.ticket_type.name))
# and mark the OPR as handed_out=True # and mark the OPR as handed_out=True
order_product.handed_out=True order_product.handed_out = True
order_product.save() order_product.save()
self.save() self.save()
@ -210,8 +206,8 @@ class Order(CreatedUpdatedModel):
if not self.paid: if not self.paid:
messages.error(request, "Order %s is not paid, so cannot mark it as refunded!" % self.pk) messages.error(request, "Order %s is not paid, so cannot mark it as refunded!" % self.pk)
else: else:
self.refunded=True self.refunded = True
### delete any tickets related to this order # delete any tickets related to this order
if self.tickets.all(): if self.tickets.all():
messages.success(request, "Order %s marked as refunded, deleting %s tickets..." % (self.pk, self.tickets.count())) messages.success(request, "Order %s marked as refunded, deleting %s tickets..." % (self.pk, self.tickets.count()))
self.tickets.all().delete() self.tickets.all().delete()
@ -259,7 +255,6 @@ class Order(CreatedUpdatedModel):
if not self.coinify_api_invoices.exists(): if not self.coinify_api_invoices.exists():
return False return False
coinifyinvoice = None
for tempinvoice in self.coinify_api_invoices.all(): for tempinvoice in self.coinify_api_invoices.all():
# we already have a coinifyinvoice for this order, check if it expired # we already have a coinifyinvoice for this order, check if it expired
if not tempinvoice.expired: if not tempinvoice.expired:
@ -512,7 +507,7 @@ class CoinifyAPIInvoice(CreatedUpdatedModel):
@property @property
def expired(self): def expired(self):
return parse_datetime(self.invoicejson['expire_time']) < timezone.now() return parse_datetime(self.invoicejson['expire_time']) < timezone.now()
class CoinifyAPICallback(CreatedUpdatedModel): class CoinifyAPICallback(CreatedUpdatedModel):

View File

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

View File

@ -1,6 +1,5 @@
from django.conf.urls import url from django.conf.urls import url
from .views import * from .views import *
from tickets.views import ShopTicketListView
urlpatterns = [ urlpatterns = [
url(r'^$', ShopIndexView.as_view(), name='index'), url(r'^$', ShopIndexView.as_view(), name='index'),

View File

@ -3,21 +3,23 @@ from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import Count, F from django.db.models import Count, F
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest, Http404 from django.http import (
HttpResponse,
HttpResponseRedirect,
HttpResponseBadRequest,
Http404
)
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.generic import ( from django.views.generic import (
View, View,
TemplateView,
ListView, ListView,
DetailView, DetailView,
FormView, FormView,
UpdateView,
) )
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
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.utils.dateparse import parse_datetime
from django.utils import timezone from django.utils import timezone
from shop.models import ( from shop.models import (
@ -27,24 +29,22 @@ from shop.models import (
ProductCategory, ProductCategory,
EpayCallback, EpayCallback,
EpayPayment, EpayPayment,
CoinifyAPIInvoice,
CoinifyAPICallback,
CreditNote, CreditNote,
) )
from .forms import AddToOrderForm from .forms import AddToOrderForm
from .epay import calculate_epay_hash, validate_epay_callback from .epay import calculate_epay_hash, validate_epay_callback
from collections import OrderedDict from collections import OrderedDict
from vendor.coinify.coinify_api import CoinifyAPI
from vendor.coinify.coinify_callback import CoinifyCallback from vendor.coinify.coinify_callback import CoinifyCallback
from .coinify import create_coinify_invoice, save_coinify_callback, process_coinify_invoice_json from .coinify import (
import json, time create_coinify_invoice,
save_coinify_callback,
process_coinify_invoice_json
)
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
# Mixins
#################################################################################
### Mixins
class EnsureCreditNoteHasPDFMixin(SingleObjectMixin): class EnsureCreditNoteHasPDFMixin(SingleObjectMixin):
model = CreditNote model = CreditNote
@ -92,7 +92,9 @@ class EnsureUnpaidOrderMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
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(reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk})) return HttpResponseRedirect(
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
@ -105,7 +107,9 @@ class EnsurePaidOrderMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
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(reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk})) return HttpResponseRedirect(
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
@ -118,7 +122,9 @@ 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(reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk})) return HttpResponseRedirect(
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
@ -160,15 +166,16 @@ class EnsureOrderHasInvoicePDFMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
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(reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk})) return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk})
)
return super(EnsureOrderHasInvoicePDFMixin, self).dispatch( return super(EnsureOrderHasInvoicePDFMixin, self).dispatch(
request, *args, **kwargs request, *args, **kwargs
) )
################################################################################# # Shop views
### Shop views
class ShopIndexView(ListView): class ShopIndexView(ListView):
model = Product model = Product
template_name = "shop_index.html" template_name = "shop_index.html"
@ -215,7 +222,7 @@ class ProductDetailView(FormView, DetailView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not self.get_object().category.public: if not self.get_object().category.public:
### this product is not publicly available # this product is not publicly available
raise Http404("Product not found") raise Http404("Product not found")
return super(ProductDetailView, self).dispatch( return super(ProductDetailView, self).dispatch(
@ -264,7 +271,9 @@ class ProductDetailView(FormView, DetailView):
return super(ProductDetailView, self).form_valid(form) return super(ProductDetailView, self).form_valid(form)
def get_success_url(self): def get_success_url(self):
return Order.objects.get(user=self.request.user, open__isnull=False).get_absolute_url() return Order.objects.get(
user=self.request.user, open__isnull=False
).get_absolute_url()
class OrderListView(LoginRequiredMixin, ListView): class OrderListView(LoginRequiredMixin, ListView):
@ -289,7 +298,9 @@ class OrderDetailView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureOrderH
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(request, "You need to accept the general terms and conditions before you can continue!")
return HttpResponseRedirect(reverse_lazy('shop:order_detail', kwargs={'pk': order.pk})) return HttpResponseRedirect(
reverse_lazy('shop:order_detail', kwargs={'pk': order.pk})
)
# 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
@ -389,9 +400,7 @@ class OrderMarkAsPaidView(LoginRequiredMixin, SingleObjectMixin, View):
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'
@ -435,18 +444,18 @@ class EpayCallbackView(SingleObjectMixin, View):
return HttpResponse(status=400) return HttpResponse(status=400)
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, callback=callback,
txnid=query.get('txnid'), 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)
else: else:
logger.error("valid epay callback with wrong amount detected") logger.error("valid epay callback with wrong amount detected")
@ -464,15 +473,16 @@ class EpayThanksView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureClosedO
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(reverse('shop:epay_thanks', kwargs={'pk': self.get_object().pk})) return HttpResponseRedirect(
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
@ -489,16 +499,14 @@ class BankTransferView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpai
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
@ -511,7 +519,9 @@ class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUn
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(reverse_lazy('shop:order_detail', kwargs={'pk': self.get_object().pk})) return HttpResponseRedirect(
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
@ -544,14 +554,17 @@ class CoinifyCallbackView(SingleObjectMixin, View):
# 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':
coinifyinvoice = process_coinify_invoice_json(callbackobject.payload['data'], self.get_object()) process_coinify_invoice_json(
callbackobject.payload['data'],
self.get_object()
)
return HttpResponse('OK') return HttpResponse('OK')
else: else:
logger.error("unsupported callback event %s" % callbackobject.payload['event']) logger.error("unsupported callback event %s" % callbackobject.payload['event'])