Skip to content

Commit

Permalink
Initial shopping cart functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
nbuonin committed May 31, 2020
1 parent 4e59a96 commit d88daf8
Show file tree
Hide file tree
Showing 19 changed files with 786 additions and 19 deletions.
6 changes: 5 additions & 1 deletion decruck/main/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Custom Forms for Decruck Wagatail"""
from django.forms import (
Form, CharField, IntegerField, ChoiceField, MultipleChoiceField,
ValidationError, RadioSelect, CheckboxSelectMultiple
ValidationError, RadioSelect, CheckboxSelectMultiple, EmailField
)
from decruck.main.models import Genre, Instrument

Expand Down Expand Up @@ -62,3 +62,7 @@ def clean(self):
if start_year > end_year:
raise ValidationError(
'The starting year can not be greater than the end year.')


class OrderRetrievalForm(Form):
email_address = EmailField()
39 changes: 39 additions & 0 deletions decruck/main/migrations/0022_order_orderitem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 2.2.12 on 2020-05-28 01:00

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('ipn', '0008_auto_20181128_1032'),
('main', '0021_auto_20200525_0116'),
]

operations = [
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
('first_name', models.CharField(max_length=256)),
('last_name', models.CharField(max_length=256)),
('email', models.EmailField(max_length=254)),
('total', models.DecimalField(decimal_places=2, max_digits=5)),
('paypay_ipn', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='order', to='ipn.PayPalIPN')),
],
),
migrations.CreateModel(
name='OrderItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('item', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='main.ScorePage')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='main.Order')),
],
),
]
64 changes: 64 additions & 0 deletions decruck/main/migrations/0023_auto_20200530_2114.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Generated by Django 2.2.12 on 2020-05-30 21:14

import decruck.main.models
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('main', '0022_order_orderitem'),
]

operations = [
migrations.AddField(
model_name='order',
name='initiator_ip',
field=models.GenericIPAddressField(default='0.0.0.0'),
preserve_default=False,
),
migrations.AddField(
model_name='order',
name='order_id',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.AddField(
model_name='order',
name='status',
field=models.CharField(choices=[('INITIATED', 'Initiated'), ('PAYMENT_RECEIVED', 'Payment Received'), ('FILE_LINKS_SENT', 'File Links Sent')], default='INITIATED', max_length=24),
),
migrations.AlterField(
model_name='order',
name='email',
field=models.EmailField(blank=True, max_length=254),
),
migrations.AlterField(
model_name='order',
name='first_name',
field=models.CharField(blank=True, max_length=256),
),
migrations.AlterField(
model_name='order',
name='last_name',
field=models.CharField(blank=True, max_length=256),
),
migrations.AlterField(
model_name='order',
name='paypay_ipn',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='order', to='ipn.PayPalIPN'),
),
migrations.CreateModel(
name='OrderItemLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('expires', models.DateTimeField(default=decruck.main.models.plus_one_day)),
('accessed', models.DateTimeField(auto_now=True)),
('access_ip', models.GenericIPAddressField()),
('slug', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('order_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='links', to='main.OrderItem')),
],
),
]
18 changes: 18 additions & 0 deletions decruck/main/migrations/0024_auto_20200531_0032.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.12 on 2020-05-31 00:32

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('main', '0023_auto_20200530_2114'),
]

operations = [
migrations.RenameField(
model_name='order',
old_name='order_id',
new_name='uuid',
),
]
19 changes: 19 additions & 0 deletions decruck/main/migrations/0025_auto_20200531_0032.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.2.12 on 2020-05-31 00:32

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('main', '0024_auto_20200531_0032'),
]

operations = [
migrations.AlterField(
model_name='order',
name='paypay_ipn',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='order', to='ipn.PayPalIPN'),
),
]
178 changes: 167 additions & 11 deletions decruck/main/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from datetime import datetime, timedelta
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.db.models import (
CharField, DecimalField, DurationField, FileField, ForeignKey, Model,
PROTECT, URLField, DateField, CASCADE, PositiveSmallIntegerField,
ImageField
ImageField, DateTimeField, EmailField, UUIDField, GenericIPAddressField
)
from django.shortcuts import render
from django.http import HttpResponse, Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse
from django.utils.safestring import mark_safe
from edtf import parse_edtf, struct_time_to_date
from edtf.parser.edtf_exceptions import EDTFParseException
import hashlib
from modelcluster.fields import ParentalManyToManyField, ParentalKey
from paypal.standard.forms import PayPalPaymentsForm
from paypal.standard.ipn.models import PayPalIPN
import uuid
from wagtail.admin.edit_handlers import (
StreamFieldPanel, FieldPanel, InlinePanel
)
Expand Down Expand Up @@ -458,7 +465,82 @@ def toggle_score_in_cart(request, score_pk):
return True


class ScorePage(Page):
class Order(Model):
INITIATED = 'INITIATED'
PAYMENT_RECEIVED = 'PAYMENT_RECEIVED'
FILE_LINKS_SENT = 'FILE_LINKS_SENT'
STATUS_CHOICES = [
(INITIATED, 'Initiated'),
(PAYMENT_RECEIVED, 'Payment Received'),
(FILE_LINKS_SENT, 'File Links Sent')
]
created = DateTimeField(auto_now_add=True)
modified = DateTimeField(auto_now=True)
uuid = UUIDField(default=uuid.uuid4, unique=True, editable=False)
status = CharField(
max_length=24, choices=STATUS_CHOICES, default=INITIATED)
initiator_ip = GenericIPAddressField()
paypay_ipn = ForeignKey(
PayPalIPN,
on_delete=PROTECT,
null=True,
related_name='order'
)
first_name = CharField(max_length=256, blank=True)
last_name = CharField(max_length=256, blank=True)
email = EmailField(blank=True)
total = DecimalField(max_digits=5, decimal_places=2)

@property
def order_number(self):
return 1000 + self.pk


class OrderItem(Model):
created = DateTimeField(auto_now_add=True)
modified = DateTimeField(auto_now=True)
order = ForeignKey(
'Order',
on_delete=CASCADE,
related_name='items'
)
item = ForeignKey(
'ScorePage',
on_delete=PROTECT,
related_name='+'
)
price = DecimalField(max_digits=5, decimal_places=2)


def plus_one_day():
return datetime.now() + timedelta(days=1)


class OrderItemLink(Model):
created = DateTimeField(auto_now_add=True)
expires = DateTimeField(default=plus_one_day)
accessed = DateTimeField(auto_now=True)
access_ip = GenericIPAddressField()
slug = UUIDField(default=uuid.uuid4, unique=True, editable=False)
order_item = ForeignKey(
'OrderItem',
on_delete=CASCADE,
related_name='links'
)

@property
def relative_url(self):
return self.order_item.item.url + str(self.slug) + '/'

@property
def full_url(self):
return self.order_item.item.full_url + str(self.slug) + '/'

def is_expired(self):
return self.expires < datetime.now()


class ScorePage(RoutablePageMixin, Page):
cover_image = ForeignKey(
'wagtailimages.Image',
null=True,
Expand Down Expand Up @@ -535,7 +617,25 @@ def clean(self):

self.preview_score_checked = True

def serve(self, request, *args, **kwargs):
@route(r'^([\w-]+)/$')
def get_score_file(self, request, score_link_slug):
if request.method == 'GET':
item_link = get_object_or_404(OrderItemLink, slug=score_link_slug)

if item_link.is_expired():
raise Http404()

item_link.access_ip = request.META.get('REMOTE_ADDR', '0.0.0.0')
item_link.save()

return render(request, "main/score_page_download.html", {
'page': self,
})
else:
raise Http404()

@route(r'^$')
def score(self, request):
if request.method == 'POST':
in_cart = toggle_score_in_cart(request, self.pk)
return render(request, "main/score_page.html", {
Expand Down Expand Up @@ -601,24 +701,80 @@ def thank_you(self, request):
context = self.get_context(request)
return render(request, "main/shopping_cart_thank_you.html", context)

@route(r'^$')
def contact_form(self, request):
if request.method == 'POST':
# Validate the form input
# If valid, remove the item from the cart
toggle_score_in_cart(request, None)
else:
@route(r'remove/(\d+)/$')
def remove_from_cart(self, request, item_pk):
pk = int(item_pk)
if request.method == 'POST' and score_in_cart(request, pk):
toggle_score_in_cart(request, pk)

return redirect(self.url)

@route(r'confirmation/$')
def order_confirmation(self, request):
if request.method == 'GET':
items = None
total = 0.00
if 'shopping_cart' in request.session and request.session['shopping_cart']:
items = ScorePage.objects.filter(pk__in=request.session['shopping_cart'])
total = sum([i.price for i in items])
else:
# If there is nothing in the cart, redirect to the cart page
return redirect(self.url)

if not items:
# If for some reason the items can't be found
return redirect(self.url)

order = Order.objects.create(
status=Order.INITIATED,
initiator_ip=request.META.get('REMOTE_ADDR', '0.0.0.0'),
total=total
)

for i in items:
OrderItem.objects.create(order=order, item=i, price=i.price)

ctx = self.get_context(request)

form_data = {
'business': settings.PAYPAL_ACCT_EMAIL,
'amount': str(total),
'notify_url': request.build_absolute_uri(
reverse('paypal-ipn')),
'return': request.build_absolute_uri(
self.reverse_subpage('thank_you')),
'cancel_return': request.build_absolute_uri(self.url),
'item_name': 'Scores by Fernande Breilh Decruck',
'item_number': str(order.uuid)
}

ctx['items'] = items
ctx['total'] = total
ctx['checkout_form'] = PayPalPaymentsForm(initial=form_data)
return render(request, "main/shopping_cart_confirmation.html", ctx)

@route(r'^$')
def cart_page(self, request):
if request.method == 'GET':
items = None
total = 0.00
if 'shopping_cart' in request.session and request.session['shopping_cart']:
items = ScorePage.objects.filter(
pk__in=request.session['shopping_cart'])
total = sum([i.price for i in items])

ctx = self.get_context(request)

ctx['items'] = items
ctx['total'] = total
return render(request, "main/shopping_cart.html", ctx)

if request.method == 'POST':
return redirect(
self.url + self.reverse_subpage('order_confirmation'))

return HttpResponse(status_code='405')


class BasicPage(Page, MenuPageMixin):
body = StreamField([
Expand Down
Loading

0 comments on commit d88daf8

Please sign in to comment.