commit
ba3eaa6546
|
@ -1,5 +1,4 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import ProductCategory, Product
|
||||
|
||||
|
||||
|
@ -12,5 +11,3 @@ class ProductCategoryAdmin(admin.ModelAdmin):
|
|||
class ProductAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'price', 'category', 'in_stock']
|
||||
list_editable = ['in_stock']
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.db import models
|
||||
|
||||
from utils.models import CampRelatedModel
|
||||
|
||||
|
||||
|
|
|
@ -6,4 +6,3 @@ class MenuView(ListView):
|
|||
model = ProductCategory
|
||||
template_name = "bar_menu.html"
|
||||
context_object_name = "categories"
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
from django.conf import settings
|
||||
from .models import Camp
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def camp(request):
|
||||
|
|
|
@ -17,10 +17,8 @@ class CampViewMixin(object):
|
|||
if queryset:
|
||||
# check if we have a foreignkey to Camp, filter if so
|
||||
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)
|
||||
|
||||
# Camp relation not found, or queryset is empty, return it unaltered
|
||||
return queryset
|
||||
|
||||
|
||||
|
|
|
@ -2,12 +2,14 @@ from camps.models import Camp
|
|||
from django.utils import timezone
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
def get_current_camp():
|
||||
try:
|
||||
return Camp.objects.get(camp__contains=timezone.now())
|
||||
except Camp.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
class CampPropertyListFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
SimpleListFilter to filter models by camp when camp is
|
||||
|
@ -27,7 +29,6 @@ class CampPropertyListFilter(admin.SimpleListFilter):
|
|||
for camp in unique_camps:
|
||||
yield (camp.slug, camp.title)
|
||||
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
# if self.value() is None return everything
|
||||
if not self.value():
|
||||
|
@ -45,4 +46,3 @@ class CampPropertyListFilter(admin.SimpleListFilter):
|
|||
if item.camp != camp:
|
||||
queryset = queryset.exclude(pk=item.pk)
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
from django.contrib import admin
|
||||
from .models import *
|
||||
from .models import (
|
||||
InfoItem,
|
||||
InfoCategory
|
||||
)
|
||||
|
||||
|
||||
@admin.register(InfoItem)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
@ -11,22 +11,22 @@ class InfoCategory(CampRelatedModel):
|
|||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
related_name = 'infocategories',
|
||||
on_delete = models.PROTECT
|
||||
related_name='infocategories',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
headline = models.CharField(
|
||||
max_length = 100,
|
||||
help_text = "The headline of this info category"
|
||||
max_length=100,
|
||||
help_text="The headline of this info category"
|
||||
)
|
||||
|
||||
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(
|
||||
help_text = 'Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.',
|
||||
default = 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,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
|
@ -45,26 +45,26 @@ class InfoItem(CampRelatedModel):
|
|||
|
||||
category = models.ForeignKey(
|
||||
'info.InfoCategory',
|
||||
related_name = 'infoitems',
|
||||
on_delete = models.PROTECT
|
||||
related_name='infoitems',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
headline = models.CharField(
|
||||
max_length = 100,
|
||||
help_text = "Headline of this info item."
|
||||
max_length=100,
|
||||
help_text="Headline of this info item."
|
||||
)
|
||||
|
||||
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(
|
||||
help_text = 'Body of this info item. Markdown is supported.'
|
||||
help_text='Body of this info item. Markdown is supported.'
|
||||
)
|
||||
|
||||
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.',
|
||||
default = 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,
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -78,5 +78,3 @@ class InfoItem(CampRelatedModel):
|
|||
|
||||
def __str__(self):
|
||||
return '%s (%s)' % (self.headline, self.category)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.contrib import admin
|
||||
from .models import *
|
||||
from .models import OutgoingIrcMessage
|
||||
|
||||
admin.site.register(OutgoingIrcMessage)
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.conf import settings
|
||||
import logging, irc3
|
||||
import logging
|
||||
import irc3
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger('bornhack.%s' % __name__)
|
||||
|
||||
|
@ -24,5 +25,5 @@ def do_work():
|
|||
irc3.IrcBot(**config).run(forever=True)
|
||||
except Exception as E:
|
||||
logger.exception("Got exception inside do_work for %s" % self.workermodule)
|
||||
raise
|
||||
raise E
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.db import models
|
||||
from django.utils import encoding
|
||||
from django.utils.text import slugify
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,6 +1,7 @@
|
|||
from django.views.generic import ListView, DetailView
|
||||
from django.utils import timezone
|
||||
from .models import *
|
||||
from .models import NewsItem
|
||||
|
||||
|
||||
class NewsIndex(ListView):
|
||||
model = NewsItem
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from .models import Profile
|
||||
|
||||
|
||||
@admin.register(Profile)
|
||||
class OrderAdmin(admin.ModelAdmin):
|
||||
actions = ['approve_public_credit_names']
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,7 +1,14 @@
|
|||
from channels.generic.websockets import JsonWebsocketConsumer
|
||||
|
||||
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):
|
||||
|
|
|
@ -3,8 +3,9 @@ from django.shortcuts import redirect
|
|||
from django.urls import reverse
|
||||
from . import models
|
||||
from django.contrib import messages
|
||||
import sys, mimetypes
|
||||
from django.http import Http404, HttpResponse
|
||||
import sys
|
||||
import mimetypes
|
||||
|
||||
|
||||
class EnsureCFSOpenMixin(SingleObjectMixin):
|
||||
|
@ -12,7 +13,9 @@ class EnsureCFSOpenMixin(SingleObjectMixin):
|
|||
# do not permit editing if call for speakers is not open
|
||||
if not self.camp.call_for_speakers_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
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -23,8 +26,10 @@ class CreateProposalMixin(SingleObjectMixin):
|
|||
# set camp and user before saving
|
||||
form.instance.camp = self.camp
|
||||
form.instance.user = self.request.user
|
||||
speaker = form.save()
|
||||
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
form.save()
|
||||
return redirect(
|
||||
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
)
|
||||
|
||||
|
||||
class EnsureUnapprovedProposalMixin(SingleObjectMixin):
|
||||
|
@ -54,7 +59,9 @@ class EnsureUserOwnsProposalMixin(SingleObjectMixin):
|
|||
# make sure that this proposal belongs to the logged in user
|
||||
if self.get_object().user.username != request.user.username:
|
||||
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
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -83,7 +90,10 @@ class PictureViewMixin(SingleObjectMixin):
|
|||
def get_picture_response(self, path):
|
||||
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
|
||||
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:
|
||||
# make nginx serve the picture using X-Accel-Redirect
|
||||
# (this works for nginx only, other webservers use x-sendfile)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import uuid
|
||||
import os
|
||||
import icalendar
|
||||
import CommonMark
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
|
@ -10,12 +9,9 @@ from django.contrib.postgres.fields import DateTimeRangeField
|
|||
from django.contrib import messages
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.dispatch import receiver
|
||||
from django.utils.text import slugify
|
||||
from django.conf import settings
|
||||
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.urls import reverse
|
||||
from django.apps import apps
|
||||
|
@ -644,7 +640,6 @@ class Speaker(CampRelatedModel):
|
|||
def get_large_picture_url(self):
|
||||
return self.get_picture_url('large')
|
||||
|
||||
|
||||
def serialize(self):
|
||||
data = {
|
||||
'name': self.name,
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,4 +1,3 @@
|
|||
import datetime
|
||||
import logging
|
||||
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 import messages
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
from django.template import Engine, Context
|
||||
|
||||
import icalendar
|
||||
|
@ -34,7 +32,7 @@ from . import models
|
|||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
||||
############## ical calendar ########################################################
|
||||
# ical calendar
|
||||
|
||||
|
||||
class ICSView(CampViewMixin, View):
|
||||
|
@ -90,7 +88,7 @@ class ICSView(CampViewMixin, View):
|
|||
return response
|
||||
|
||||
|
||||
############## proposals ########################################################
|
||||
# proposals
|
||||
|
||||
|
||||
class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
|
||||
|
@ -241,7 +239,7 @@ class EventProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsP
|
|||
template_name = 'eventproposal_detail.html'
|
||||
|
||||
|
||||
################## speakers ###############################################
|
||||
# speakers
|
||||
|
||||
|
||||
@method_decorator(require_safe, name='dispatch')
|
||||
|
@ -268,7 +266,7 @@ class SpeakerListView(CampViewMixin, ListView):
|
|||
template_name = 'speaker_list.html'
|
||||
|
||||
|
||||
################## events ##############################################
|
||||
# events
|
||||
|
||||
|
||||
class EventListView(CampViewMixin, ListView):
|
||||
|
@ -281,7 +279,7 @@ class EventDetailView(CampViewMixin, DetailView):
|
|||
template_name = 'schedule_event_detail.html'
|
||||
|
||||
|
||||
################## schedule #############################################
|
||||
# schedule
|
||||
|
||||
|
||||
class NoScriptScheduleView(CampViewMixin, TemplateView):
|
||||
|
@ -293,13 +291,12 @@ class NoScriptScheduleView(CampViewMixin, TemplateView):
|
|||
return context
|
||||
|
||||
|
||||
|
||||
class ScheduleView(CampViewMixin, TemplateView):
|
||||
template_name = 'schedule_overview.html'
|
||||
|
||||
def get_context_data(self, *args, **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
|
||||
|
||||
|
||||
|
@ -308,8 +305,8 @@ class CallForSpeakersView(CampViewMixin, TemplateView):
|
|||
return '%s_call_for_speakers.html' % self.camp.slug
|
||||
|
||||
|
||||
# control center
|
||||
|
||||
################## control center #############################################
|
||||
|
||||
class ProgramControlCenter(CampViewMixin, TemplateView):
|
||||
template_name = "control/index.html"
|
||||
|
|
|
@ -40,6 +40,7 @@ def available_from(product):
|
|||
return "None"
|
||||
available_from.short_description = 'Available from'
|
||||
|
||||
|
||||
def available_to(product):
|
||||
if product.available_in.upper:
|
||||
return product.available_in.upper.strftime("%c")
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.apps import AppConfig
|
|||
import logging
|
||||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
||||
class ShopConfig(AppConfig):
|
||||
name = 'shop'
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from vendor.coinify.coinify_api import CoinifyAPI
|
||||
from vendor.coinify.coinify_callback import CoinifyCallback
|
||||
from .models import CoinifyAPIRequest, CoinifyAPIInvoice, CoinifyAPICallback
|
||||
from django.conf import settings
|
||||
import json, logging
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
@ -34,7 +35,7 @@ def save_coinify_callback(request, order):
|
|||
# now attempt to parse json
|
||||
try:
|
||||
parsed = json.loads(request.body.decode('utf-8'))
|
||||
except Exception as E:
|
||||
except Exception:
|
||||
parsed = ''
|
||||
|
||||
# save this callback to db
|
||||
|
@ -89,8 +90,8 @@ def handle_coinify_api_response(req, order):
|
|||
if req.response['success']:
|
||||
# save this new coinify invoice to the DB
|
||||
coinifyinvoice = process_coinify_invoice_json(
|
||||
invoicejson = req.response['data'],
|
||||
order = order,
|
||||
invoicejson=req.response['data'],
|
||||
order=order,
|
||||
)
|
||||
return coinifyinvoice
|
||||
else:
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
def current_order(request):
|
||||
if request.user.is_authenticated():
|
||||
order = None
|
||||
|
@ -11,5 +8,3 @@ def current_order(request):
|
|||
|
||||
return {'current_order': order}
|
||||
return {}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import hashlib
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def calculate_epay_hash(order, request):
|
||||
hashstring = (
|
||||
'{merchant_number}{description}11{amount}DKK'
|
||||
|
@ -10,9 +11,9 @@ def calculate_epay_hash(order, request):
|
|||
description=order.description,
|
||||
amount=order.total*100,
|
||||
order_id=order.pk,
|
||||
accept_url = order.get_epay_accept_url(request),
|
||||
cancel_url = order.get_cancel_url(request),
|
||||
callback_url = order.get_epay_callback_url(request),
|
||||
accept_url=order.get_epay_accept_url(request),
|
||||
cancel_url=order.get_cancel_url(request),
|
||||
callback_url=order.get_epay_callback_url(request),
|
||||
md5_secret=settings.EPAY_MD5_SECRET,
|
||||
)
|
||||
epay_hash = hashlib.md5(hashstring.encode('utf-8')).hexdigest()
|
||||
|
@ -24,6 +25,7 @@ def validate_epay_callback(query):
|
|||
for key, value in query.items():
|
||||
if key != 'hash':
|
||||
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']
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django import forms
|
||||
from .models import Order
|
||||
|
||||
|
||||
class AddToOrderForm(forms.Form):
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from psycopg2.extras import DateTimeTZRange
|
||||
|
||||
from django.db.models import QuerySet
|
||||
from django.utils import timezone
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import io
|
||||
import logging
|
||||
import hashlib
|
||||
import base64
|
||||
import qrcode
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
@ -202,7 +198,7 @@ class Order(CreatedUpdatedModel):
|
|||
if request:
|
||||
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
|
||||
order_product.handed_out=True
|
||||
order_product.handed_out = True
|
||||
order_product.save()
|
||||
self.save()
|
||||
|
||||
|
@ -210,8 +206,8 @@ class Order(CreatedUpdatedModel):
|
|||
if not self.paid:
|
||||
messages.error(request, "Order %s is not paid, so cannot mark it as refunded!" % self.pk)
|
||||
else:
|
||||
self.refunded=True
|
||||
### delete any tickets related to this order
|
||||
self.refunded = True
|
||||
# delete any tickets related to this order
|
||||
if self.tickets.all():
|
||||
messages.success(request, "Order %s marked as refunded, deleting %s tickets..." % (self.pk, self.tickets.count()))
|
||||
self.tickets.all().delete()
|
||||
|
@ -259,7 +255,6 @@ class Order(CreatedUpdatedModel):
|
|||
if not self.coinify_api_invoices.exists():
|
||||
return False
|
||||
|
||||
coinifyinvoice = None
|
||||
for tempinvoice in self.coinify_api_invoices.all():
|
||||
# we already have a coinifyinvoice for this order, check if it expired
|
||||
if not tempinvoice.expired:
|
||||
|
@ -512,7 +507,7 @@ class CoinifyAPIInvoice(CreatedUpdatedModel):
|
|||
|
||||
@property
|
||||
def expired(self):
|
||||
return parse_datetime(self.invoicejson['expire_time']) < timezone.now()
|
||||
return parse_datetime(self.invoicejson['expire_time']) < timezone.now()
|
||||
|
||||
|
||||
class CoinifyAPICallback(CreatedUpdatedModel):
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,6 +1,5 @@
|
|||
from django.conf.urls import url
|
||||
from .views import *
|
||||
from tickets.views import ShopTicketListView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', ShopIndexView.as_view(), name='index'),
|
||||
|
|
|
@ -3,21 +3,23 @@ from django.contrib import messages
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
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.views.generic import (
|
||||
View,
|
||||
TemplateView,
|
||||
ListView,
|
||||
DetailView,
|
||||
FormView,
|
||||
UpdateView,
|
||||
)
|
||||
from django.views.generic.base import RedirectView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils import timezone
|
||||
|
||||
from shop.models import (
|
||||
|
@ -27,24 +29,22 @@ from shop.models import (
|
|||
ProductCategory,
|
||||
EpayCallback,
|
||||
EpayPayment,
|
||||
CoinifyAPIInvoice,
|
||||
CoinifyAPICallback,
|
||||
CreditNote,
|
||||
)
|
||||
from .forms import AddToOrderForm
|
||||
from .epay import calculate_epay_hash, validate_epay_callback
|
||||
from collections import OrderedDict
|
||||
from vendor.coinify.coinify_api import CoinifyAPI
|
||||
from vendor.coinify.coinify_callback import CoinifyCallback
|
||||
from .coinify import create_coinify_invoice, save_coinify_callback, process_coinify_invoice_json
|
||||
import json, time
|
||||
from .coinify import (
|
||||
create_coinify_invoice,
|
||||
save_coinify_callback,
|
||||
process_coinify_invoice_json
|
||||
)
|
||||
import logging
|
||||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
||||
|
||||
#################################################################################
|
||||
### Mixins
|
||||
# Mixins
|
||||
class EnsureCreditNoteHasPDFMixin(SingleObjectMixin):
|
||||
model = CreditNote
|
||||
|
||||
|
@ -92,7 +92,9 @@ class EnsureUnpaidOrderMixin(SingleObjectMixin):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.get_object().paid:
|
||||
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(
|
||||
request, *args, **kwargs
|
||||
|
@ -105,7 +107,9 @@ class EnsurePaidOrderMixin(SingleObjectMixin):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.get_object().paid:
|
||||
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(
|
||||
request, *args, **kwargs
|
||||
|
@ -118,7 +122,9 @@ class EnsureClosedOrderMixin(SingleObjectMixin):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.get_object().open is not None:
|
||||
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(
|
||||
request, *args, **kwargs
|
||||
|
@ -160,15 +166,16 @@ class EnsureOrderHasInvoicePDFMixin(SingleObjectMixin):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.get_object().invoice.pdf:
|
||||
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(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
#################################################################################
|
||||
### Shop views
|
||||
# Shop views
|
||||
class ShopIndexView(ListView):
|
||||
model = Product
|
||||
template_name = "shop_index.html"
|
||||
|
@ -215,7 +222,7 @@ class ProductDetailView(FormView, DetailView):
|
|||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.get_object().category.public:
|
||||
### this product is not publicly available
|
||||
# this product is not publicly available
|
||||
raise Http404("Product not found")
|
||||
|
||||
return super(ProductDetailView, self).dispatch(
|
||||
|
@ -264,7 +271,9 @@ class ProductDetailView(FormView, DetailView):
|
|||
return super(ProductDetailView, self).form_valid(form)
|
||||
|
||||
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):
|
||||
|
@ -289,7 +298,9 @@ class OrderDetailView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureOrderH
|
|||
if payment_method in order.PAYMENT_METHODS:
|
||||
if not request.POST.get('accept_terms'):
|
||||
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
|
||||
order.payment_method = payment_method
|
||||
|
@ -389,9 +400,7 @@ class OrderMarkAsPaidView(LoginRequiredMixin, SingleObjectMixin, View):
|
|||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
|
||||
#################################################################################
|
||||
### Epay views
|
||||
|
||||
# Epay views
|
||||
class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView):
|
||||
model = Order
|
||||
template_name = 'epay_form.html'
|
||||
|
@ -435,18 +444,18 @@ class EpayCallbackView(SingleObjectMixin, View):
|
|||
return HttpResponse(status=400)
|
||||
|
||||
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')
|
||||
|
||||
### 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:
|
||||
### create an EpayPayment object linking the callback to the order
|
||||
# create an EpayPayment object linking the callback to the order
|
||||
EpayPayment.objects.create(
|
||||
order=order,
|
||||
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)
|
||||
else:
|
||||
logger.error("valid epay callback with wrong amount detected")
|
||||
|
@ -464,15 +473,16 @@ class EpayThanksView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureClosedO
|
|||
if request.GET:
|
||||
# epay redirects the user back to our accepturl with a long
|
||||
# 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(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
#################################################################################
|
||||
### Bank Transfer view
|
||||
# Bank Transfer view
|
||||
|
||||
class BankTransferView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureOrderHasProductsMixin, DetailView):
|
||||
model = Order
|
||||
|
@ -489,16 +499,14 @@ class BankTransferView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpai
|
|||
return context
|
||||
|
||||
|
||||
#################################################################################
|
||||
### Cash payment view
|
||||
# Cash payment view
|
||||
|
||||
class CashView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureOrderHasProductsMixin, DetailView):
|
||||
model = Order
|
||||
template_name = 'cash.html'
|
||||
|
||||
|
||||
#################################################################################
|
||||
### Coinify views
|
||||
# Coinify views
|
||||
|
||||
class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, SingleObjectMixin, RedirectView):
|
||||
model = Order
|
||||
|
@ -511,7 +519,9 @@ class CoinifyRedirectView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUn
|
|||
coinifyinvoice = create_coinify_invoice(order, request)
|
||||
if not coinifyinvoice:
|
||||
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(
|
||||
request, *args, **kwargs
|
||||
|
@ -544,14 +554,17 @@ class CoinifyCallbackView(SingleObjectMixin, View):
|
|||
# attemt to validate the callbackc
|
||||
if sdk.validate_callback(request.body, request.META['HTTP_X_COINIFY_CALLBACK_SIGNATURE']):
|
||||
# mark callback as valid in db
|
||||
callbackobject.valid=True
|
||||
callbackobject.valid = True
|
||||
callbackobject.save()
|
||||
else:
|
||||
logger.error("invalid coinify callback detected")
|
||||
return HttpResponseBadRequest('something is fucky')
|
||||
|
||||
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')
|
||||
else:
|
||||
logger.error("unsupported callback event %s" % callbackobject.payload['event'])
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ class Sponsor(CampRelatedModel):
|
|||
def camp(self):
|
||||
return self.tier.camp
|
||||
|
||||
|
||||
class SponsorTier(CampRelatedModel):
|
||||
name = models.CharField(
|
||||
max_length=25,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ class EnsureWritableCampMixin(SingleObjectMixin):
|
|||
# do not permit view if camp is in readonly mode
|
||||
if self.camp.read_only:
|
||||
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
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue