Getting there!
This commit is contained in:
parent
326e9d70cd
commit
c45a3aa8c9
|
@ -1,7 +1,7 @@
|
||||||
def current_order(request):
|
def current_order(request):
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated():
|
||||||
order = None
|
order = None
|
||||||
orders = request.user.orders.filter(finalized=False)
|
orders = request.user.orders.filter(open__isnull=False)
|
||||||
|
|
||||||
if orders:
|
if orders:
|
||||||
order = orders[0]
|
order = orders[0]
|
||||||
|
|
|
@ -2,10 +2,6 @@ from django import forms
|
||||||
from .models import Order
|
from .models import Order
|
||||||
|
|
||||||
|
|
||||||
class CheckoutForm(forms.Form):
|
|
||||||
# no fields here, just three submit buttons
|
|
||||||
pass
|
|
||||||
|
|
||||||
class AddToOrderForm(forms.Form):
|
class AddToOrderForm(forms.Form):
|
||||||
quantity = forms.IntegerField()
|
quantity = forms.IntegerField()
|
||||||
|
|
||||||
|
|
27
shop/migrations/0004_auto_20160515_1604.py
Normal file
27
shop/migrations/0004_auto_20160515_1604.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.6 on 2016-05-15 16:04
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0003_auto_20160513_0646'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='productcategory',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(default=''),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='product',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='shop.ProductCategory'),
|
||||||
|
),
|
||||||
|
]
|
21
shop/migrations/0005_product_slug.py
Normal file
21
shop/migrations/0005_product_slug.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.6 on 2016-05-15 16:14
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0004_auto_20160515_1604'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(default=''),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
27
shop/migrations/0006_ensure_slugs.py
Normal file
27
shop/migrations/0006_ensure_slugs.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.6 on 2016-05-15 16:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_slugs(apps, schema_editor):
|
||||||
|
ProductCategory = apps.get_model('shop', 'ProductCategory')
|
||||||
|
Product = apps.get_model('shop', 'Product')
|
||||||
|
|
||||||
|
for category in ProductCategory.objects.all():
|
||||||
|
category.save()
|
||||||
|
|
||||||
|
for product in Product.objects.all():
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0005_product_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(ensure_slugs, migrations.RunPython.noop)
|
||||||
|
]
|
28
shop/migrations/0007_auto_20160515_2157.py
Normal file
28
shop/migrations/0007_auto_20160515_2157.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.6 on 2016-05-15 21:57
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0006_ensure_slugs'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='open',
|
||||||
|
field=models.NullBooleanField(default=True, help_text='Whether this order is open or not. "None" means closed.', verbose_name='Open?'),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='order',
|
||||||
|
name='finalized',
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='order',
|
||||||
|
unique_together=set([('user', 'open')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.aggregates import Sum
|
from django.db.models.aggregates import Sum
|
||||||
from django.contrib.postgres.fields import DateTimeRangeField, JSONField
|
from django.contrib.postgres.fields import DateTimeRangeField, JSONField
|
||||||
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from bornhack.utils import CreatedUpdatedModel, UUIDModel
|
from bornhack.utils import CreatedUpdatedModel, UUIDModel
|
||||||
|
@ -9,6 +10,9 @@ from .managers import ProductQuerySet
|
||||||
|
|
||||||
class Order(CreatedUpdatedModel):
|
class Order(CreatedUpdatedModel):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('user', 'open')
|
||||||
|
|
||||||
products = models.ManyToManyField(
|
products = models.ManyToManyField(
|
||||||
'shop.Product',
|
'shop.Product',
|
||||||
through='shop.OrderProductRelation'
|
through='shop.OrderProductRelation'
|
||||||
|
@ -27,10 +31,10 @@ class Order(CreatedUpdatedModel):
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
finalized = models.BooleanField(
|
open = models.NullBooleanField(
|
||||||
verbose_name=_('Finalized?'),
|
verbose_name=_('Open?'),
|
||||||
help_text=_('Whether this order has been finalized.'),
|
help_text=_('Whether this order is open or not. "None" means closed.'),
|
||||||
default=False,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
camp = models.ForeignKey(
|
camp = models.ForeignKey(
|
||||||
|
@ -44,6 +48,12 @@ class Order(CreatedUpdatedModel):
|
||||||
BANK_TRANSFER = 'bank_transfer'
|
BANK_TRANSFER = 'bank_transfer'
|
||||||
|
|
||||||
PAYMENT_METHODS = [
|
PAYMENT_METHODS = [
|
||||||
|
CREDIT_CARD,
|
||||||
|
BLOCKCHAIN,
|
||||||
|
BANK_TRANSFER,
|
||||||
|
]
|
||||||
|
|
||||||
|
PAYMENT_METHOD_CHOICES = [
|
||||||
(CREDIT_CARD, 'Credit card'),
|
(CREDIT_CARD, 'Credit card'),
|
||||||
(BLOCKCHAIN, 'Blockchain'),
|
(BLOCKCHAIN, 'Blockchain'),
|
||||||
(BANK_TRANSFER, 'Bank transfer'),
|
(BANK_TRANSFER, 'Bank transfer'),
|
||||||
|
@ -51,7 +61,7 @@ class Order(CreatedUpdatedModel):
|
||||||
|
|
||||||
payment_method = models.CharField(
|
payment_method = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PAYMENT_METHODS,
|
choices=PAYMENT_METHOD_CHOICES,
|
||||||
default=BLOCKCHAIN
|
default=BLOCKCHAIN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,10 +77,15 @@ class ProductCategory(CreatedUpdatedModel, UUIDModel):
|
||||||
verbose_name_plural = 'Product categories'
|
verbose_name_plural = 'Product categories'
|
||||||
|
|
||||||
name = models.CharField(max_length=150)
|
name = models.CharField(max_length=150)
|
||||||
|
slug = models.SlugField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super(ProductCategory, self).save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Product(CreatedUpdatedModel, UUIDModel):
|
class Product(CreatedUpdatedModel, UUIDModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -78,9 +93,13 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
||||||
verbose_name_plural = 'Products'
|
verbose_name_plural = 'Products'
|
||||||
ordering = ['available_in']
|
ordering = ['available_in']
|
||||||
|
|
||||||
category = models.ForeignKey('shop.ProductCategory')
|
category = models.ForeignKey(
|
||||||
|
'shop.ProductCategory',
|
||||||
|
related_name='products'
|
||||||
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=150)
|
name = models.CharField(max_length=150)
|
||||||
|
slug = models.SlugField()
|
||||||
|
|
||||||
price = models.IntegerField(
|
price = models.IntegerField(
|
||||||
help_text=_('Price of the product (in DKK).')
|
help_text=_('Price of the product (in DKK).')
|
||||||
|
@ -103,6 +122,10 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
||||||
self.price,
|
self.price,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super(Product, self).save(**kwargs)
|
||||||
|
|
||||||
def is_available(self):
|
def is_available(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
return now in self.available_in
|
return now in self.available_in
|
||||||
|
@ -114,6 +137,10 @@ class OrderProductRelation(models.Model):
|
||||||
quantity = models.PositiveIntegerField()
|
quantity = models.PositiveIntegerField()
|
||||||
handed_out = models.BooleanField(default=False)
|
handed_out = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total(self):
|
||||||
|
return self.product.price * self.quantity
|
||||||
|
|
||||||
|
|
||||||
class EpayCallback(CreatedUpdatedModel, UUIDModel):
|
class EpayCallback(CreatedUpdatedModel, UUIDModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -1,7 +1,48 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<b>details for order {{ order.id }}</b>
|
<h1>Order #{{ order.id }}</h1>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Name
|
||||||
|
<th>
|
||||||
|
Quantity
|
||||||
|
<th>
|
||||||
|
Price
|
||||||
|
<th>
|
||||||
|
Total
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for order_product in order.orderproductrelation_set.all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ order_product.product.name }}
|
||||||
|
<td>
|
||||||
|
{{ order_product.quantity }}
|
||||||
|
<td>
|
||||||
|
{{ order_product.product.price }}
|
||||||
|
<td>
|
||||||
|
{{ order_product.total }}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{# TODO: Add total + VAT info #}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if order.open %}
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_button "Credit card" button_type="submit" button_class="btn-primary" name="payment_method" value="credit_card" %}
|
||||||
|
{% bootstrap_button "Blockchain" button_type="submit" button_class="btn-primary" name="payment_method" value="blockchain" %}
|
||||||
|
{% bootstrap_button "Bank transfer" button_type="submit" button_class="btn-primary" name="payment_method" value="bank_transfer" %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
36
shop/templates/order_list.html
Normal file
36
shop/templates/order_list.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h3>Orders</h3>
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order ID</th>
|
||||||
|
<th>Open?</th>
|
||||||
|
<th>Paid?</th>
|
||||||
|
<th>Delivered?</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for order in orders %}
|
||||||
|
<tr {% if order.finalized and order.paid %}style="color: lightgreen"{% endif %}>
|
||||||
|
<td>{{ order.id }}</td>
|
||||||
|
<td>{{ order.finalized }}</td>
|
||||||
|
<td>{{ order.paid }}</td>
|
||||||
|
<td>?</td>
|
||||||
|
<td>
|
||||||
|
{% if order.finalized and not order.paid %}
|
||||||
|
{% url 'shop:order_detail' pk=order.pk as order_detail_url %}
|
||||||
|
{% url 'shop:order_cancel' pk=order.pk as order_cancel_url %}
|
||||||
|
{% bootstrap_button "Pay order" href=order_detail_url button_class="btn-primary" %}
|
||||||
|
{% bootstrap_button "Cancel order" href=order_cancel_url button_class="btn-primary" %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
{% bootstrap_button "Add to order" button_type="submit" button_class="btn-primary" %}
|
{% bootstrap_button "Add to order" button_type="submit" button_class="btn-primary" %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,73 +1,61 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h2>Shop</h2>
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
Categories:
|
||||||
|
{% for category in categories %}
|
||||||
|
<a href="{% url 'shop:index' %}?category={{category.slug}}"
|
||||||
|
class="label label-{% if category.slug == current_category %}primary{% else %}default{% endif %}">
|
||||||
|
{{category}}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
{% for product in products %}
|
||||||
|
|
||||||
|
<a href="{% url 'shop:product_detail' slug=product.slug %}">
|
||||||
|
<div class="col-sm-4 col-lg-4 col-md-4">
|
||||||
|
<div class="thumbnail">
|
||||||
|
<img src="http://placehold.it/320x200" alt="">
|
||||||
|
<div class="caption">
|
||||||
|
<h5>{{ product.category.name }}</h5>
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
{{ product.name }}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
Price: {{ product.price }} DKK
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ product.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
<strong>Availability:</strong><br/>
|
||||||
|
{{ product.available_in.lower|date:"Y-m-d H:i" }}
|
||||||
|
{% if product.available_in.upper %}
|
||||||
|
- {{ product.available_in.upper|date:"Y-m-d H:i" }}
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="lead">
|
|
||||||
Here you can see your orders and available products in the shop.
|
|
||||||
</p>
|
|
||||||
<h3>Orders</h3>
|
|
||||||
<table class="table table-bordered table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Order ID</th>
|
|
||||||
<th>Open?</th>
|
|
||||||
<th>Paid?</th>
|
|
||||||
<th>Delivered?</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for order in orders %}
|
|
||||||
<tr {% if order.finalized and order.paid %}style="color: lightgreen"{% endif %}>
|
|
||||||
<td>{{ order.id }}</td>
|
|
||||||
<td>{{ order.finalized }}</td>
|
|
||||||
<td>{{ order.paid }}</td>
|
|
||||||
<td>?</td>
|
|
||||||
<td>
|
|
||||||
{% if order.finalized and not order.paid %}
|
|
||||||
{% url 'shop:order_detail' pk=order.id as order_detail_url %}
|
|
||||||
{% url 'shop:order_cancel' pk=order.id as order_cancel_url %}
|
|
||||||
{% bootstrap_button "Pay order" href=order_detail_url button_class="btn-primary" %}
|
|
||||||
{% bootstrap_button "Cancel order" href=order_cancel_url button_class="btn-primary" %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h3>Products</h3>
|
|
||||||
<table class="table table-bordered table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Price</th>
|
|
||||||
<th>Availability</th>
|
|
||||||
<th>Buy</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for product in product_list %}
|
|
||||||
<tr {% if not product.is_available %}style="color: lightgrey"{%endif%}>
|
|
||||||
<td>{{ product.name }}</td>
|
|
||||||
<td>{{ product.price }} DKK</td>
|
|
||||||
<td>{{ product.available_in.lower }}
|
|
||||||
{% if product.available_in.upper %}
|
|
||||||
- {{ product.available_in.upper }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if product.is_available %}
|
|
||||||
{% url 'shop:product_detail' pk=product.pk as product_detail_url %}
|
|
||||||
{% bootstrap_button "Add to order" href=product_detail_url button_class="btn-primary" %}
|
|
||||||
{% else %}
|
|
||||||
N/A
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -13,7 +13,8 @@ urlpatterns = [
|
||||||
#name='epay_callback'
|
#name='epay_callback'
|
||||||
#),
|
#),
|
||||||
url(r'^$', ShopIndexView.as_view(), name='index'),
|
url(r'^$', ShopIndexView.as_view(), name='index'),
|
||||||
url(r'products/(?P<pk>[a-zA-Z0-9\-]+)/$', ProductDetailView.as_view(), name='product_detail'),
|
url(r'products/(?P<slug>[-_\w+]+)/$', ProductDetailView.as_view(), name='product_detail'),
|
||||||
|
url(r'orders/$', OrderListView.as_view(), name='order_list'),
|
||||||
url(r'orders/(?P<pk>[0-9]+)/$', OrderDetailView.as_view(), name='order_detail'),
|
url(r'orders/(?P<pk>[0-9]+)/$', OrderDetailView.as_view(), name='order_detail'),
|
||||||
url(r'orders/(?P<pk>[0-9]+)/checkout/$', CheckoutView.as_view(), name='checkout'),
|
# url(r'orders/(?P<pk>[0-9]+)/checkout/$', CheckoutView.as_view(), name='checkout'),
|
||||||
]
|
]
|
||||||
|
|
221
shop/views.py
221
shop/views.py
|
@ -1,70 +1,60 @@
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect, Http404
|
|
||||||
from django.views.generic import CreateView, TemplateView, ListView, DetailView, View, FormView
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from django.db.models import Count, F
|
||||||
|
from django.http import HttpResponseRedirect, Http404
|
||||||
|
from django.views.generic import (
|
||||||
|
TemplateView,
|
||||||
|
ListView,
|
||||||
|
DetailView,
|
||||||
|
FormView,
|
||||||
|
)
|
||||||
|
|
||||||
from .models import Order, Product, EpayCallback, EpayPayment, OrderProductRelation
|
from shop.models import (
|
||||||
from .forms import CheckoutForm, AddToOrderForm
|
Order,
|
||||||
|
Product,
|
||||||
|
OrderProductRelation,
|
||||||
|
ProductCategory,
|
||||||
|
)
|
||||||
|
from .forms import AddToOrderForm
|
||||||
|
|
||||||
|
|
||||||
class ShopIndexView(ListView):
|
class ShopIndexView(ListView):
|
||||||
model = Product
|
model = Product
|
||||||
template_name = "shop_index.html"
|
template_name = "shop_index.html"
|
||||||
|
context_object_name = 'products'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ShopIndexView, self).get_context_data(**kwargs)
|
context = super(ShopIndexView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
if 'category' in self.request.GET:
|
||||||
|
category = self.request.GET.get('category')
|
||||||
|
context['products'] = context['products'].filter(
|
||||||
|
category__slug=category
|
||||||
|
)
|
||||||
|
context['current_category'] = category
|
||||||
|
context['categories'] = ProductCategory.objects.annotate(
|
||||||
|
num_products=Count('products')
|
||||||
|
).filter(
|
||||||
|
num_products__gt=0
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class OrderListView(LoginRequiredMixin, ListView):
|
||||||
|
model = Order
|
||||||
|
template_name = "order_list.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(OrderListView, self).get_context_data(**kwargs)
|
||||||
context['orders'] = Order.objects.filter(user=self.request.user)
|
context['orders'] = Order.objects.filter(user=self.request.user)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ProductDetailView(LoginRequiredMixin, FormView):
|
|
||||||
model = Product
|
|
||||||
template_name = 'product_detail.html'
|
|
||||||
form_class = AddToOrderForm
|
|
||||||
context_object_name = 'product'
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.product = Product.objects.get(pk=kwargs.get('pk'))
|
|
||||||
return self.render_to_response(self.get_context_data())
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
### do we have an open order?
|
|
||||||
try:
|
|
||||||
order = Order.objects.get(user=self.request.user, finalized=False)
|
|
||||||
except Order.DoesNotExist:
|
|
||||||
### no open order - open a new one
|
|
||||||
order = Order.objects.create(user=request.user)
|
|
||||||
|
|
||||||
### get product from kwargs
|
|
||||||
if self.product in order.products.all():
|
|
||||||
### this product is already added to this order, increase count by one
|
|
||||||
OrderProductRelation.objects.filter(product=self.product, order=order).update(quantity=F('quantity') + 1)
|
|
||||||
else:
|
|
||||||
order.products.add(self.product)
|
|
||||||
|
|
||||||
### done
|
|
||||||
return super(ProductDetailView, self).form_valid(form)
|
|
||||||
|
|
||||||
|
|
||||||
class OrderDetailView(LoginRequiredMixin, DetailView):
|
class OrderDetailView(LoginRequiredMixin, DetailView):
|
||||||
model = Product
|
|
||||||
template_name = 'order_detail.html'
|
|
||||||
context_object_name = 'order'
|
|
||||||
|
|
||||||
|
|
||||||
class CheckoutView(LoginRequiredMixin, FormView):
|
|
||||||
"""
|
|
||||||
Shows a summary of all products contained in an order,
|
|
||||||
total price, VAT info, and a button to finalize order and go to payment
|
|
||||||
"""
|
|
||||||
model = Order
|
model = Order
|
||||||
template_name = 'checkout.html'
|
template_name = 'order_detail.html'
|
||||||
form_class = CheckoutForm
|
|
||||||
context_object_name = 'order'
|
context_object_name = 'order'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -79,44 +69,97 @@ class CheckoutView(LoginRequiredMixin, FormView):
|
||||||
messages.error(request, 'This order contains no products!')
|
messages.error(request, 'This order contains no products!')
|
||||||
return HttpResponseRedirect('shop:order_detail')
|
return HttpResponseRedirect('shop:order_detail')
|
||||||
|
|
||||||
return self.render_to_response(self.get_context_data())
|
return super(OrderDetailView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# mark order as finalized and redirect user to payment
|
||||||
|
order = self.get_object()
|
||||||
|
payment_method = request.POST.get('payment_method')
|
||||||
|
|
||||||
|
if payment_method in order.PAYMENT_METHODS:
|
||||||
|
order.payment_method = payment_method
|
||||||
|
else:
|
||||||
|
# unknown submit button
|
||||||
|
messages.error(self.request, 'Unknown submit button :(')
|
||||||
|
return reverse_lazy(
|
||||||
|
'shop:checkout',
|
||||||
|
kwargs={'orderid': self.get_object.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
order.finalized = True
|
||||||
|
|
||||||
|
reverses = {
|
||||||
|
Order.CREDIT_CARD: reverse_lazy(
|
||||||
|
'shop:epay_form',
|
||||||
|
kwargs={'orderid': order.id}
|
||||||
|
),
|
||||||
|
Order.BLOCKCHAIN: reverse_lazy(
|
||||||
|
'shop:coinify_pay',
|
||||||
|
kwargs={'orderid': order.id}
|
||||||
|
),
|
||||||
|
Order.BANK_TRANSFER: reverse_lazy(
|
||||||
|
'shop:bank_transfer',
|
||||||
|
kwargs={'orderid': order.id}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponseRedirect(reverses[payment_method])
|
||||||
|
|
||||||
|
|
||||||
|
class ProductDetailView(LoginRequiredMixin, FormView, DetailView):
|
||||||
|
model = Product
|
||||||
|
template_name = 'product_detail.html'
|
||||||
|
form_class = AddToOrderForm
|
||||||
|
context_object_name = 'product'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
### mark order as finalized and redirect user to payment
|
product = self.get_object()
|
||||||
form.instance.finalized=True
|
quantity = form.cleaned_data.get('quantity'),
|
||||||
|
|
||||||
### set payment_method based on submit button used
|
# do we have an open order?
|
||||||
if 'credit_card' in form.data:
|
try:
|
||||||
form.instance.payment_method=='credit_card'
|
order = Order.objects.get(
|
||||||
elif 'blockchain' in form.data:
|
user=self.request.user,
|
||||||
form.instance.payment_method=='blockchain'
|
finalized=False
|
||||||
elif 'bank_transfer' in form.data:
|
)
|
||||||
form.instance.payment_method=='bank_transfer'
|
except Order.DoesNotExist:
|
||||||
|
# no open order - open a new one
|
||||||
|
order = Order.objects.create(user=self.request.user)
|
||||||
|
|
||||||
|
# get product from kwargs
|
||||||
|
if product in order.products.all():
|
||||||
|
# this product is already added to this order,
|
||||||
|
# increase count by quantity
|
||||||
|
OrderProductRelation.objects.filter(
|
||||||
|
product=product,
|
||||||
|
order=order
|
||||||
|
).update(quantity=F('quantity') + quantity)
|
||||||
else:
|
else:
|
||||||
### unknown submit button
|
order.products.add(product)
|
||||||
messages.error(request, 'Unknown submit button :(')
|
|
||||||
return reverse('shop:checkout', kwargs={'orderid': self.get_object.id})
|
|
||||||
|
|
||||||
return super(CheckoutView, self).form_valid(form)
|
messages.info(
|
||||||
|
self.request,
|
||||||
|
'{}x {} has been added to your order.'.format(
|
||||||
|
quantity,
|
||||||
|
product.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# done
|
||||||
|
return super(ProductDetailView, self).form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if self.get_object.payment_method == 'credit_card':
|
return reverse_lazy(
|
||||||
return reverse('shop:epay_form', kwargs={'orderid': self.get_object.id})
|
'shop:product_detail',
|
||||||
elif self.get_object.payment_method == 'blockchain':
|
kwargs={'slug': self.get_object().slug}
|
||||||
return reverse('shop:coinify_pay', kwargs={'orderid': self.get_object.id})
|
)
|
||||||
elif self.get_object.payment_method == 'bank_transfer':
|
|
||||||
return reverse('shop:bank_transfer', kwargs={'orderid': self.get_object.id})
|
|
||||||
else:
|
|
||||||
### unknown payment method
|
|
||||||
messages.error(request, 'Unknown payment method :(')
|
|
||||||
return reverse('shop:checkout', kwargs={'orderid': self.get_object.id})
|
|
||||||
|
|
||||||
|
|
||||||
class CoinifyRedirectView(TemplateView):
|
class CoinifyRedirectView(TemplateView):
|
||||||
template_name = 'coinify_redirect.html'
|
template_name = 'coinify_redirect.html'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
### validate a few things
|
# validate a few things
|
||||||
self.order = Order.objects.get(pk=kwargs.get('order_id'))
|
self.order = Order.objects.get(pk=kwargs.get('order_id'))
|
||||||
if self.order.user != request.user:
|
if self.order.user != request.user:
|
||||||
raise Http404("Order not found")
|
raise Http404("Order not found")
|
||||||
|
@ -139,25 +182,37 @@ class CoinifyRedirectView(TemplateView):
|
||||||
order = Order.objects.get(pk=kwargs.get('order_id'))
|
order = Order.objects.get(pk=kwargs.get('order_id'))
|
||||||
context = super(CoinifyRedirectView, self).get_context_data(**kwargs)
|
context = super(CoinifyRedirectView, self).get_context_data(**kwargs)
|
||||||
context['order'] = order
|
context['order'] = order
|
||||||
|
|
||||||
### Initiate coinify API and create invoice
|
# Initiate coinify API and create invoice
|
||||||
coinifyapi = CoinifyAPI(settings.COINIFY_API_KEY, settings.COINIFY_API_SECRET)
|
coinifyapi = CoinifyAPI(
|
||||||
|
settings.COINIFY_API_KEY,
|
||||||
|
settings.COINIFY_API_SECRET
|
||||||
|
)
|
||||||
response = coinifyapi.invoice_create(
|
response = coinifyapi.invoice_create(
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
plugin_name='BornHack 2016 webshop',
|
plugin_name='BornHack 2016 webshop',
|
||||||
plugin_version='1.0',
|
plugin_version='1.0',
|
||||||
description='BornHack 2016 order id #%s' % order.id,
|
description='BornHack 2016 order id #%s' % order.id,
|
||||||
callback_url=reverse('shop:coinfy_callback', kwargs={'orderid': order.id}),
|
callback_url=reverse(
|
||||||
return_url=reverse('shop:order_paid', kwargs={'orderid': order.id}),
|
'shop:coinfy_callback',
|
||||||
|
kwargs={'orderid': order.id}
|
||||||
|
),
|
||||||
|
return_url=reverse(
|
||||||
|
'shop:order_paid',
|
||||||
|
kwargs={'orderid': order.id}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not response['success']:
|
if not response['success']:
|
||||||
api_error = response['error']
|
api_error = response['error']
|
||||||
print "API error: %s (%s)" % (api_error['message'], api_error['code'] )
|
print "API error: %s (%s)" % (
|
||||||
|
api_error['message'],
|
||||||
|
api_error['code']
|
||||||
|
)
|
||||||
|
|
||||||
invoice = response['data']
|
invoice = response['data']
|
||||||
### change this to pass only needed data when we get that far
|
# change this to pass only needed data when we get that far
|
||||||
context['invoice'] = invoice
|
context['invoice'] = invoice
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue