Merge pull request #202 from bornhack/st/cleanup

Cleanup
This commit is contained in:
Stephan Telling 2018-03-04 16:17:50 +01:00 committed by GitHub
commit ba3eaa6546
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 134 additions and 151 deletions

View file

@ -1,5 +1,4 @@
from django.contrib import admin from django.contrib import admin
from .models import ProductCategory, Product from .models import ProductCategory, Product
@ -12,5 +11,3 @@ class ProductCategoryAdmin(admin.ModelAdmin):
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'category', 'in_stock'] list_display = ['name', 'price', 'category', 'in_stock']
list_editable = ['in_stock'] list_editable = ['in_stock']

View file

@ -1,5 +1,3 @@
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -1,5 +1,4 @@
from django.db import models from django.db import models
from utils.models import CampRelatedModel from utils.models import CampRelatedModel

View file

@ -6,4 +6,3 @@ class MenuView(ListView):
model = ProductCategory model = ProductCategory
template_name = "bar_menu.html" template_name = "bar_menu.html"
context_object_name = "categories" context_object_name = "categories"

View file

@ -1,6 +1,4 @@
from django.conf import settings
from .models import Camp from .models import Camp
from django.utils import timezone
def camp(request): def camp(request):

View file

@ -17,10 +17,8 @@ class CampViewMixin(object):
if queryset: if queryset:
# check if we have a foreignkey to Camp, filter if so # check if we have a foreignkey to Camp, filter if so
for field in queryset.model._meta.fields: for field in queryset.model._meta.fields:
if field.name=="camp" and field.related_model._meta.label == "camps.Camp": if field.name == "camp" and field.related_model._meta.label == "camps.Camp":
return queryset.filter(camp=self.camp) return queryset.filter(camp=self.camp)
# Camp relation not found, or queryset is empty, return it unaltered # Camp relation not found, or queryset is empty, return it unaltered
return queryset return queryset

View file

@ -2,12 +2,14 @@ from camps.models import Camp
from django.utils import timezone from django.utils import timezone
from django.contrib import admin from django.contrib import admin
def get_current_camp(): def get_current_camp():
try: try:
return Camp.objects.get(camp__contains=timezone.now()) return Camp.objects.get(camp__contains=timezone.now())
except Camp.DoesNotExist: except Camp.DoesNotExist:
return False return False
class CampPropertyListFilter(admin.SimpleListFilter): class CampPropertyListFilter(admin.SimpleListFilter):
""" """
SimpleListFilter to filter models by camp when camp is SimpleListFilter to filter models by camp when camp is
@ -27,7 +29,6 @@ class CampPropertyListFilter(admin.SimpleListFilter):
for camp in unique_camps: for camp in unique_camps:
yield (camp.slug, camp.title) yield (camp.slug, camp.title)
def queryset(self, request, queryset): def queryset(self, request, queryset):
# if self.value() is None return everything # if self.value() is None return everything
if not self.value(): if not self.value():
@ -45,4 +46,3 @@ class CampPropertyListFilter(admin.SimpleListFilter):
if item.camp != camp: if item.camp != camp:
queryset = queryset.exclude(pk=item.pk) queryset = queryset.exclude(pk=item.pk)
return queryset return queryset

View file

@ -1,5 +1,8 @@
from django.contrib import admin from django.contrib import admin
from .models import * from .models import (
InfoItem,
InfoCategory
)
@admin.register(InfoItem) @admin.register(InfoItem)

View file

@ -1,5 +1,3 @@
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -11,22 +11,22 @@ class InfoCategory(CampRelatedModel):
camp = models.ForeignKey( camp = models.ForeignKey(
'camps.Camp', 'camps.Camp',
related_name = 'infocategories', related_name='infocategories',
on_delete = models.PROTECT on_delete=models.PROTECT
) )
headline = models.CharField( headline = models.CharField(
max_length = 100, max_length=100,
help_text = "The headline of this info category" help_text="The headline of this info category"
) )
anchor = models.SlugField( anchor = models.SlugField(
help_text = "The HTML anchor to use for this info category." help_text="The HTML anchor to use for this info category."
) )
weight = models.PositiveIntegerField( weight = models.PositiveIntegerField(
help_text = 'Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.', help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.',
default = 100, default=100,
) )
def clean(self): def clean(self):
@ -45,26 +45,26 @@ class InfoItem(CampRelatedModel):
category = models.ForeignKey( category = models.ForeignKey(
'info.InfoCategory', 'info.InfoCategory',
related_name = 'infoitems', related_name='infoitems',
on_delete = models.PROTECT on_delete=models.PROTECT
) )
headline = models.CharField( headline = models.CharField(
max_length = 100, max_length=100,
help_text = "Headline of this info item." help_text="Headline of this info item."
) )
anchor = models.SlugField( anchor = models.SlugField(
help_text = "The HTML anchor to use for this info item." help_text="The HTML anchor to use for this info item."
) )
body = models.TextField( body = models.TextField(
help_text = 'Body of this info item. Markdown is supported.' help_text='Body of this info item. Markdown is supported.'
) )
weight = models.PositiveIntegerField( weight = models.PositiveIntegerField(
help_text = 'Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.', help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.',
default = 100, default=100,
) )
@property @property
@ -78,5 +78,3 @@ class InfoItem(CampRelatedModel):
def __str__(self): def __str__(self):
return '%s (%s)' % (self.headline, self.category) return '%s (%s)' % (self.headline, self.category)

View file

@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from .models import * from .models import OutgoingIrcMessage
admin.site.register(OutgoingIrcMessage) admin.site.register(OutgoingIrcMessage)

View file

@ -1,5 +1,3 @@
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -1,5 +1,6 @@
from django.conf import settings from django.conf import settings
import logging, irc3 import logging
import irc3
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('bornhack.%s' % __name__) logger = logging.getLogger('bornhack.%s' % __name__)
@ -24,5 +25,5 @@ def do_work():
irc3.IrcBot(**config).run(forever=True) irc3.IrcBot(**config).run(forever=True)
except Exception as E: except Exception as E:
logger.exception("Got exception inside do_work for %s" % self.workermodule) logger.exception("Got exception inside do_work for %s" % self.workermodule)
raise raise E

View file

@ -1,5 +1,3 @@
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -1,5 +1,3 @@
from django.db import models from django.db import models
from django.utils import encoding from django.utils import encoding
from django.utils.text import slugify from django.utils.text import slugify

View file

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

View file

@ -1,6 +1,7 @@
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
from django.utils import timezone from django.utils import timezone
from .models import * from .models import NewsItem
class NewsIndex(ListView): class NewsIndex(ListView):
model = NewsItem model = NewsItem

View file

@ -1,6 +1,7 @@
from django.contrib import admin from django.contrib import admin
from .models import Profile from .models import Profile
@admin.register(Profile) @admin.register(Profile)
class OrderAdmin(admin.ModelAdmin): class OrderAdmin(admin.ModelAdmin):
actions = ['approve_public_credit_names'] actions = ['approve_public_credit_names']

View file

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

View file

@ -1,7 +1,14 @@
from channels.generic.websockets import JsonWebsocketConsumer from channels.generic.websockets import JsonWebsocketConsumer
from camps.models import Camp from camps.models import Camp
from .models import Event, EventInstance, Favorite, EventLocation, EventType, Speaker from .models import (
Event,
EventInstance,
Favorite,
EventLocation,
EventType,
Speaker
)
class ScheduleConsumer(JsonWebsocketConsumer): class ScheduleConsumer(JsonWebsocketConsumer):

View file

@ -3,8 +3,9 @@ from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from . import models from . import models
from django.contrib import messages from django.contrib import messages
import sys, mimetypes
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
import sys
import mimetypes
class EnsureCFSOpenMixin(SingleObjectMixin): class EnsureCFSOpenMixin(SingleObjectMixin):
@ -12,7 +13,9 @@ class EnsureCFSOpenMixin(SingleObjectMixin):
# do not permit editing if call for speakers is not open # do not permit editing if call for speakers is not open
if not self.camp.call_for_speakers_open: if not self.camp.call_for_speakers_open:
messages.error(request, "The Call for Speakers is not open.") messages.error(request, "The Call for Speakers is not open.")
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
)
# alright, continue with the request # alright, continue with the request
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -23,8 +26,10 @@ class CreateProposalMixin(SingleObjectMixin):
# set camp and user before saving # set camp and user before saving
form.instance.camp = self.camp form.instance.camp = self.camp
form.instance.user = self.request.user form.instance.user = self.request.user
speaker = form.save() form.save()
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
)
class EnsureUnapprovedProposalMixin(SingleObjectMixin): class EnsureUnapprovedProposalMixin(SingleObjectMixin):
@ -54,7 +59,9 @@ class EnsureUserOwnsProposalMixin(SingleObjectMixin):
# make sure that this proposal belongs to the logged in user # make sure that this proposal belongs to the logged in user
if self.get_object().user.username != request.user.username: if self.get_object().user.username != request.user.username:
messages.error(request, "No thanks") messages.error(request, "No thanks")
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
)
# alright, continue with the request # alright, continue with the request
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -83,7 +90,10 @@ class PictureViewMixin(SingleObjectMixin):
def get_picture_response(self, path): def get_picture_response(self, path):
if 'runserver' in sys.argv or 'runserver_plus' in sys.argv: if 'runserver' in sys.argv or 'runserver_plus' in sys.argv:
# this is a local devserver situation, guess mimetype from extension and return picture directly # this is a local devserver situation, guess mimetype from extension and return picture directly
response = HttpResponse(self.picture, content_type=mimetypes.types_map[".%s" % self.picture.name.split(".")[-1]]) response = HttpResponse(
self.picture,
content_type=mimetypes.types_map[".%s" % self.picture.name.split(".")[-1]]
)
else: else:
# make nginx serve the picture using X-Accel-Redirect # make nginx serve the picture using X-Accel-Redirect
# (this works for nginx only, other webservers use x-sendfile) # (this works for nginx only, other webservers use x-sendfile)

View file

@ -1,7 +1,6 @@
import uuid import uuid
import os import os
import icalendar import icalendar
import CommonMark
import logging import logging
from datetime import timedelta from datetime import timedelta
@ -10,12 +9,9 @@ from django.contrib.postgres.fields import DateTimeRangeField
from django.contrib import messages from django.contrib import messages
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.dispatch import receiver
from django.utils.text import slugify from django.utils.text import slugify
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse_lazy, reverse
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.urls import reverse from django.urls import reverse
from django.apps import apps from django.apps import apps
@ -644,7 +640,6 @@ class Speaker(CampRelatedModel):
def get_large_picture_url(self): def get_large_picture_url(self):
return self.get_picture_url('large') return self.get_picture_url('large')
def serialize(self): def serialize(self):
data = { data = {
'name': self.name, 'name': self.name,

View file

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

View file

@ -1,4 +1,3 @@
import datetime
import logging import logging
import os import os
@ -12,7 +11,6 @@ from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages from django.contrib import messages
from django.urls import reverse from django.urls import reverse
from django.db.models import Q
from django.template import Engine, Context from django.template import Engine, Context
import icalendar import icalendar
@ -34,7 +32,7 @@ from . import models
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
############## ical calendar ######################################################## # ical calendar
class ICSView(CampViewMixin, View): class ICSView(CampViewMixin, View):
@ -90,7 +88,7 @@ class ICSView(CampViewMixin, View):
return response return response
############## proposals ######################################################## # proposals
class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView): class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
@ -241,7 +239,7 @@ class EventProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsP
template_name = 'eventproposal_detail.html' template_name = 'eventproposal_detail.html'
################## speakers ############################################### # speakers
@method_decorator(require_safe, name='dispatch') @method_decorator(require_safe, name='dispatch')
@ -268,7 +266,7 @@ class SpeakerListView(CampViewMixin, ListView):
template_name = 'speaker_list.html' template_name = 'speaker_list.html'
################## events ############################################## # events
class EventListView(CampViewMixin, ListView): class EventListView(CampViewMixin, ListView):
@ -281,7 +279,7 @@ class EventDetailView(CampViewMixin, DetailView):
template_name = 'schedule_event_detail.html' template_name = 'schedule_event_detail.html'
################## schedule ############################################# # schedule
class NoScriptScheduleView(CampViewMixin, TemplateView): class NoScriptScheduleView(CampViewMixin, TemplateView):
@ -293,13 +291,12 @@ class NoScriptScheduleView(CampViewMixin, TemplateView):
return context return context
class ScheduleView(CampViewMixin, TemplateView): class ScheduleView(CampViewMixin, TemplateView):
template_name = 'schedule_overview.html' template_name = 'schedule_overview.html'
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
context = super(ScheduleView, self).get_context_data(**kwargs) context = super(ScheduleView, self).get_context_data(**kwargs)
context['schedule_midnight_offset_hours'] = settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS; context['schedule_midnight_offset_hours'] = settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS
return context return context
@ -308,8 +305,8 @@ class CallForSpeakersView(CampViewMixin, TemplateView):
return '%s_call_for_speakers.html' % self.camp.slug return '%s_call_for_speakers.html' % self.camp.slug
# control center
################## control center #############################################
class ProgramControlCenter(CampViewMixin, TemplateView): class ProgramControlCenter(CampViewMixin, TemplateView):
template_name = "control/index.html" template_name = "control/index.html"

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'])

View file

@ -1,5 +1,3 @@
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -43,6 +43,7 @@ class Sponsor(CampRelatedModel):
def camp(self): def camp(self):
return self.tier.camp return self.tier.camp
class SponsorTier(CampRelatedModel): class SponsorTier(CampRelatedModel):
name = models.CharField( name = models.CharField(
max_length=25, max_length=25,

View file

@ -1,5 +1,3 @@
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -9,9 +9,9 @@ class EnsureWritableCampMixin(SingleObjectMixin):
# do not permit view if camp is in readonly mode # do not permit view if camp is in readonly mode
if self.camp.read_only: if self.camp.read_only:
messages.error(request, "No thanks") messages.error(request, "No thanks")
return redirect(reverse('village_list', kwargs={'camp_slug': self.camp.slug})) return redirect(
reverse('village_list', kwargs={'camp_slug': self.camp.slug})
)
# alright, continue with the request # alright, continue with the request
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)