Added membership pricing view. Added membership pricing html. Added membership pricing url. Started booking view. Started integrating booking.js. Added calendar date range js lib. Fixing date picker. Added function to calculate remaining free days. Updated function to calculate booking price. Added BookingDatesForm. Added backend validation for booking dates. fixed booking form. adding pricing to booking payment view. Added free days to booking payment view. Started booking payment stripe flow. Redefined dispatch method to booking view. Now login is required for payment selecting dates and booking. Added date range to booking payment. Fixed booking select dates view. Added form validations to booking form. Created a order after a confirm a booking. Added free_days to booking model in order to facilitate free_days Availability for the next booking. Creating a Stripe charge. Creating Booking model instance. Refactor booking payment views. Associated an order to a stripe charge. Associated booking order instance with a booking instance.

This commit is contained in:
Levi 2016-08-30 21:32:45 -05:00
parent 1152e41b6e
commit 2eb2c90b1f
16 changed files with 783 additions and 25 deletions

View file

@ -1,6 +1,6 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from datetime import datetime
from utils.models import BillingAddress
from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm
@ -34,3 +34,46 @@ class MembershipBillingForm(BillingAddressForm):
'postal_code': _('Postal Code'),
'country': _('Country'),
}
class BookingBillingForm(BillingAddressForm):
token = forms.CharField(widget=forms.HiddenInput())
start_date = forms.DateField(widget=forms.HiddenInput())
end_date = forms.DateField(widget=forms.HiddenInput())
price = forms.FloatField(widget=forms.HiddenInput())
class Meta:
model = BillingAddress
fields = ['start_date', 'end_date', 'price', 'street_address',
'city', 'postal_code', 'country']
labels = {
'street_address': _('Street Address'),
'city': _('City'),
'postal_code': _('Postal Code'),
'country': _('Country'),
}
class BookingDateForm(forms.Form):
start_date = forms.DateField(required=False)
end_date = forms.DateField(required=False)
date_range = forms.CharField(required=False)
def clean_date_range(self):
date_range = self.cleaned_data.get('date_range')
dates = date_range.replace(' ', '').split('-')
try:
start_date, end_date = [datetime.strptime(date_string, "%m/%d/%Y").date()
for date_string in dates]
except ValueError:
raise forms.ValidationError("Submit valid dates.")
if start_date >= end_date:
raise forms.ValidationError("Your end date must be greather than your start date.")
return start_date, end_date
def clean(self):
start_date, end_date = self.cleaned_data.get('date_range')
self.cleaned_data['start_date'] = start_date
self.cleaned_data['end_date'] = end_date
return self.cleaned_data

View file

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-08-25 05:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('utils', '0002_billingaddress'),
('membership', '0006_auto_20160526_0445'),
('digitalglarus', '0008_auto_20160820_1959'),
]
operations = [
migrations.CreateModel(
name='Booking',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_date', models.DateField()),
('end_date', models.DateField()),
('price', models.FloatField()),
('membership', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='digitalglarus.Membership')),
],
),
migrations.CreateModel(
name='BookingOrder',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('approved', models.BooleanField(default=False)),
('last4', models.CharField(max_length=4)),
('cc_brand', models.CharField(max_length=10)),
('stripe_charge_id', models.CharField(max_length=100, null=True)),
('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')),
('booking', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='digitalglarus.Booking')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='BookingPrices',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('price_per_day', models.FloatField()),
],
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-08-28 17:45
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0009_booking_bookingorder_bookingprices'),
]
operations = [
migrations.RenameModel(
old_name='BookingPrices',
new_name='BookingPrice',
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-08-28 21:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0010_auto_20160828_1745'),
]
operations = [
migrations.AddField(
model_name='bookingprice',
name='special_price_offer',
field=models.FloatField(default=290.0),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-08-30 03:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0011_bookingprice_special_price_offer'),
]
operations = [
migrations.AddField(
model_name='booking',
name='free_days',
field=models.IntegerField(default=0),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-08-30 04:56
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0012_booking_free_days'),
]
operations = [
migrations.RemoveField(
model_name='booking',
name='membership',
),
]

25
digitalglarus/mixins.py Normal file
View file

@ -0,0 +1,25 @@
from django.db import models
from membership.models import StripeCustomer
from utils.models import BillingAddress
class Ordereable(models.Model):
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True)
class Meta:
abstract = True
@classmethod
def create(cls, data):
stripe_charge = data.pop('stripe_charge', None)
instance = cls.objects.create(**data)
instance.stripe_charge_id = stripe_charge.id
instance.last4 = stripe_charge.source.last4
instance.cc_brand = stripe_charge.source.brand
return instance

View file

@ -6,9 +6,10 @@ from cms.models import CMSPlugin
from filer.fields.image import FilerImageField
from django.core.urlresolvers import reverse
from django.utils.functional import cached_property
from .mixins import Ordereable
from membership.models import StripeCustomer
from utils.models import BillingAddress
# from membership.models import StripeCustomer
# from utils.models import BillingAddress
class MembershipType(models.Model):
@ -64,15 +65,8 @@ class Membership(models.Model):
return instance
class MembershipOrder(models.Model):
class MembershipOrder(Ordereable, models.Model):
membership = models.ForeignKey(Membership)
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True)
@classmethod
def create(cls, data):
@ -84,6 +78,70 @@ class MembershipOrder(models.Model):
return instance
class BookingPrice(models.Model):
price_per_day = models.FloatField()
special_price_offer = models.FloatField()
class Booking(models.Model):
start_date = models.DateField()
end_date = models.DateField()
price = models.FloatField()
free_days = models.IntegerField(default=0)
@classmethod
def create(cls, data):
instance = cls.objects.create(**data)
return instance
@classmethod
def get_ramaining_free_days(cls, user):
# ZERO_DAYS = 0
# ONE_DAY = 1
TWO_DAYS = 2
current_date = datetime.today()
current_month_bookings = cls.objects.filter(bookingorder__customer__user=user,
start_date__month=current_date.month)
free_days = TWO_DAYS - sum(map(lambda x: x.days, current_month_bookings))
return free_days
# free_days = ZERO_DAYS if current_month_bookings.count() > 2 else TWO_DAYS
# if current_month_bookings.count() == 1:
# booking = current_month_bookings.get()
# booked_days = (booking.end_date - booking.start_date).days
# free_days = ONE_DAY if booked_days == 1 else ZERO_DAYS
# return free_days
# free_days = ZERO_DAYS if current_month_bookings.count() > 2 else TWO_DAYS
# return free_days
@classmethod
def booking_price(cls, user, start_date, end_date):
"""
Calculate the booking price for requested dates
How it does:
1. Check if the user has booked the current month
2. Get how many days user wants to book
3. Get price per day from BookingPrices instance
4. Get available free days
5. Calculate price by this formula -> (booking_days - free_days) * price_per_day
"""
booking_prices = BookingPrice.objects.last()
price_per_day = booking_prices.price_per_day
booking_days = (end_date - start_date).days
free_days = cls.get_ramaining_free_days(user)
final_booking_price = (booking_days - free_days) * price_per_day
original_booking_price = (booking_days) * price_per_day
return original_booking_price, final_booking_price, free_days
class BookingOrder(Ordereable, models.Model):
booking = models.OneToOneField(Booking)
class Supporter(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)

View file

@ -0,0 +1,22 @@
$( document ).ready(function() {
// $('#booking-date-range').daterangepicker();
$('#booking-date-range').daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: 'Clear'
}
});
$('#booking-date-range').on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format('MM/DD/YYYY') + ' - ' + picker.endDate.format('MM/DD/YYYY'));
});
$('#booking-date-range').on('cancel.daterangepicker', function(ev, picker) {
$(this).val('Select your dates');
});
});

View file

@ -0,0 +1,53 @@
{% extends "new_base_glarus.html" %}
{% load staticfiles cms_tags bootstrap3%}
{% block title %}crowdfunding{% endblock %}
{% block content %}
<section id="price">
<div class="signup-container">
<div class="col-xs-12 col-sm-3 col-lg-4 text-center wow fadeInDown"> </div>
<div class="col-xs-12 col-sm-6 col-lg-4 text-center wow fadeInDown">
<div class="signup-box">
<span class="glyphicon glyphicon-plus"></span>
<h2 class="section-heading">Booking</h2>
<h2 class="signup-lead">Start coworking at Digital Glarus! <br> Membership costs only
<strong>35CHF</strong> per month.<br> 2 free working days included!</h2>
<hr class="primary">
<div class="signup-form form-group row">
<input type="hidden" name="next" value="{{ request.GET.next }}">
<form action="" method="post" class="form" novalidate>
{% csrf_token %}
<input class="form-control" placeholder="Select your dates" type="text" id="booking-date-range" name="date_range">
<button type="submit" class="btn btn-primary btn-blue">Book</button>
</form>
<br>
<div class="notice-box">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-3 col-lg-4 text-center wow fadeInDown"></div>
</div>
</div>
</section>
<section id="contact">
<div class="fill">
<div class="row" class="wow fadeInDown">
<div class="col-lg-12 text-center wow fadeInDown">
<div class="col-md-4 map-title">
Digital Glarus<br>
<span class="map-caption">In der Au 7 Schwanden 8762 Switzerland
<br>info@digitalglarus.ch
<br>
(044) 534-66-22
<p>&nbsp;</p>
</span>
</div>
<p>&nbsp;</p>
</div>
</div>
</div>
</section>
{% endblock %}

View file

@ -0,0 +1,176 @@
{% extends "new_base_glarus.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<style type="text/css">
.nopadding {
padding: 0 !important;
margin: 0 !important;
}
.form-control#id_country{
-webkit-appearance: none;
-moz-appearance: none;
background-position: right 50%;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAMCAYAAABSgIzaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDZFNDEwNjlGNzFEMTFFMkJEQ0VDRTM1N0RCMzMyMkIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDZFNDEwNkFGNzFEMTFFMkJEQ0VDRTM1N0RCMzMyMkIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0NkU0MTA2N0Y3MUQxMUUyQkRDRUNFMzU3REIzMzIyQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0NkU0MTA2OEY3MUQxMUUyQkRDRUNFMzU3REIzMzIyQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuGsgwQAAAA5SURBVHjaYvz//z8DOYCJgUxAf42MQIzTk0D/M+KzkRGPoQSdykiKJrBGpOhgJFYTWNEIiEeAAAMAzNENEOH+do8AAAAASUVORK5CYII=);
padding: .5em;
padding-right: 1.5em
}
</style>
<section id="price">
<div class="signup-container">
<div class="col-xs-12 col-sm-6 col-lg-8 text-center wow fadeInDown">
<div class="payment-box">
<h2 class="section-heading payment-head">Booking</h2>
<!-- <h2 class="membership-amount">35CHF</h2> -->
<hr class="greyline-long">
<h2 class="billing-head">Billing Adress</h2>
<div class="signup-form form-group row">
<form role="form" id="billing-form" method="post" action="{% url 'digitalglarus:booking_payment' %}" novalidate>
{% for field in form %}
{% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%}
{% endfor %}
{% bootstrap_form_errors form type='non_fields'%}
{{form.errors}}
<br>
</form>
</div>
<h2 class="billing-head">Credit Card</h2>
<div class="signup-form form-group row">
<form role="form" id="payment-form" novalidate>
<div class="row">
<div class="col-xs-9 col-md-12">
<div class="form-group">
<input type="text" class="form-control" name="cardName" placeholder="Name on card" required autofocus data-stripe="name" />
</div>
</div>
</div>
<div class="row">
<div class="col-xs-9 col-md-12">
<div class="form-group">
<input type="text" class="form-control" name="cardNumber" placeholder="Valid Card Number" required data-stripe="number" />
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-6 nopadding">
<label for="expMonth">EXPIRATION DATE</label><br/>
<div class="col-xs-6 col-lg-6 col-md-6">
<div class="form-group">
<input type="text" class="form-control" name="expMonth" placeholder="MM" required data-stripe="exp_month" />
</div>
</div>
<div class="col-xs-6 col-lg-6 col-md-6 pl-ziro">
<div class="form-group">
<input type="text" class="form-control" name="expYear" placeholder="YY" required data-stripe="exp_year" />
</div>
</div>
</div>
<div class="col-xs-4 col-md-6 pull-right">
<div class="form-group">
<label for="cvCode">CV CODE</label>
<input type="password" class="form-control" name="cvCode" placeholder="CV" required data-stripe="cvc" />
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-primary btn-lg btn-blck " type="submit">Book</button>
</div>
</div>
<div class="row" style="display:none;">
<div class="col-xs-12">
<p class="payment-errors"></p>
</div>
</div>
{% if paymentError %}
<div class="row">
<div class="col-xs-12">
<p>
{% bootstrap_alert paymentError alert_type='danger' %}
</p>
</div>
</div>
{% endif %}
</form>
<br>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-4 col-lg-4 wow fadeInDown">
<div class="order-summary">
<div class="header text-center">
<h2 class="order-name">Booking Summary</h2>
</div>
<div class="order-box">
<h3 class="col-xs-6 order-item">Dates {{start_date}} - {{end_date}}</h3>
<br/>
<hr class="greyline">
<h2 class="col-xs-6 payment-total">Total days {{booking_days}} </h2>
<h2 class="order-sum">{{original_price|floatformat}}CHF</h2>
{% if free_days %}
<h2 class="col-xs-6 payment-total">Free days {{free_days}}</h2>
<h2 class="order-sum"><span class="text-danger">-{{total_discount|floatformat}}CHF</span></h2>
{% endif %}
<hr class="greyline">
<h2 class="col-xs-6 payment-total">Total</h2>
<h2 class="order-result">{{discount_price|floatformat}}CHF</h2>
<div class="text-center">
<label class="custom-control custom-checkbox">
<br/>
<input type="checkbox" class="custom-control-input">
<span class="custom-control-indicator"></span>
<span class="custom-control-description">I accept the Digital Glarus <a href=#>Terms and Conditions</a>, <a href=#>Community Guidelines</a> and <a href=#>Privacy Policy</a></span>
</label>
<div class="button-box">
</div>
<div class="button-box">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="contact">
<div class="fill">
<div class="row" class="wow fadeInDown">
<div class="col-lg-12 text-center wow fadeInDown">
<div class="col-md-4 map-title">
Digital Glarus<br>
<span class="map-caption">In der Au 7 Schwanden 8762 Switzerland
<br>info@digitalglarus.ch
<br>
(044) 534-66-22
<p>&nbsp;</p>
</span>
</div>
<p>&nbsp;</p>
</div>
</div>
</div>
</section>
<!-- stripe key data -->
{% if stripe_key %}
<script type="text/javascript">
(function () {window.stripeKey = "{{stripe_key}}";})();
</script>
{%endif%}
{% endblock %}

View file

@ -0,0 +1,87 @@
{% extends "new_base_glarus.html" %}
{% load staticfiles cms_tags bootstrap3%}
{% block title %}crowdfunding{% endblock %}
{% block content %}
<section id="price">
<div class="container">
<div class="row col-md-3 text-center wow fadeInDown"></div>
<div class="row col-md-6 text-center wow fadeInDown">
<div class="price-box">
<span class="glyphicon glyphicon-star"></span>
<h2 class="section-heading">Digital Glarus Membership</h2>
<h2 class="price"><strong>{{membership_type.price}}CHF</strong>/month<br> (free working 2days included)</h2>
<hr class="primary">
<img src="{% static 'digitalglarus/img/graph.png' %}" width="450" height="396" class="img-responsive center-block graph"><br>
<div class="price-exp-box"
<p class="carousel-text text-left supporter-black">
Get your Digital Glarus Membership for only
<strong>{{membership_type.price}}CHF</strong>
and get 2 working days for free!
The membership fee is a monthly subscription and
with every payment you help us to create a better
workspace.
You want to work more than 2 days per month in our
coworking space? Great! It's only <strong> 15CHF</strong>
per additional day.
You want more? That's really cool!
If you work 17 days a month or EVERY DAY of a month,
you can do so for <strong> 290CHF</strong>/month..
And the best? The discount is
applied automatically!
Just become a member, book your
working day and we will take care of the rest.
</p>
<div class="text-center">
<a class="btn btn-primary btn-blue" href="{% url 'digitalglarus:membership_payment' %}">become a member</a>
</div>
</div>
</div>
</div>
<div class="row col-md-3 text-center wow fadeInDown"></div>
</div>
</section>
<!--membership includes-->
<section id="membership-includes">
<div class="container">
<div class="intro-price text-center wow fadeInDown">
<div class="intro-headline-small">
<span class="intro-headline-small">
Become our member
</span>
</div>
<h3 class="intro-smaller">Our membership includes:</h3>
<div class="col-lg-12 price-list">
<li class="list-group-item row price-list">
<div class="price-list"><span class="glyphicon glyphicon-ok "></span>Super-fast Internet</div>
<div class="price-list"><span class="glyphicon glyphicon-ok glyphicon-inverse"></span>Any workspace in our common areas</div>
<div class="price-list"><span class="glyphicon glyphicon-ok "></span>Free 2 working day passes</div>
<div class="price-list"><span class="glyphicon glyphicon-ok glyphicon-inverse"></span>Access to any of our great workshops</div>
<div class="price-list"><span class="glyphicon glyphicon-ok "></span>Special invitation to our fondue nights, cooking & hacking sessions, coworking & cohiking, and many more cool stuff.</div>
</li>
</div>
</div>
</div>
</div>
</section>
<section id="contact">
<div class="fill">
<div class="row" class="wow fadeInDown">
<div class="col-lg-12 text-center wow fadeInDown">
<div class="col-md-4 map-title">
Digital Glarus<br>
<span class="map-caption">In der Au 7 Schwanden 8762 Switzerland
<br>info@digitalglarus.ch
<br>
(044) 534-66-22
<p>&nbsp;</p>
</span>
</div>
<p>&nbsp;</p>
</div>
</div>
</div>
</section>
{% endblock %}

View file

@ -18,7 +18,7 @@
<div class="signup-form form-group row">
<form action="{% url 'digitalglarus:signup' %}" method="post" class="form" novalidate>
{% csrf_token %}
<input type="hidden" name="" value="{{ request.GET.next }}">
<input type="hidden" name="next" value="{{ request.GET.next }}">
{% for field in form %}
{% bootstrap_field field show_label=False type='fields'%}
{% endfor %}

View file

@ -93,7 +93,7 @@
<a class="page-scroll" href="{% url 'digitalglarus:login' %}">Log In</a>
</li>
<li>
<a class="page-scroll" href="{% url 'digitalglarus:login' %}">Sign Up</a>
<a class="page-scroll" href="{% url 'digitalglarus:signup' %}">Sign Up</a>
</li>
</ul>
</div>
@ -167,11 +167,24 @@
<!-- Custom Fonts -->
<link href="//fonts.googleapis.com/css?family=Raleway" rel="stylesheet" type="text/css">
<link href="{% static 'digitalglarus/font-awesome-4.1.0/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Kaushan+Script" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic,700italic" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Roboto+Slab:400,100,300,700" rel="stylesheet" type="text/css">
<!-- Include Required Prerequisites -->
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
<!-- <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/bootstrap/latest/css/bootstrap.css" />
-->
<!-- Include Date Range Picker -->
<script type="text/javascript" src="//cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.js"></script>
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.css" />
<!-- Booking JavaScript -->
<script src="{% static 'digitalglarus/js/booking.js' %}"></script>
<!-- Custom Fonts -->
<link href="//fonts.googleapis.com/css?family=Raleway" rel="stylesheet" type="text/css">
<link href="{% static 'digitalglarus/font-awesome-4.1.0/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Kaushan+Script" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic,700italic" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Roboto+Slab:400,100,300,700" rel="stylesheet" type="text/css">
</html>

View file

@ -3,7 +3,8 @@ from django.utils.translation import ugettext_lazy as _
from . import views
from .views import ContactView, IndexView, AboutView, HistoryView, LoginView, SignupView,\
PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView
PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView,\
MembershipPricingView, BookingSelectDatesView, BookingPaymentView
# from membership.views import LoginRegistrationView
urlpatterns = [
@ -11,13 +12,19 @@ urlpatterns = [
url(_(r'contact/?$'), ContactView.as_view(), name='contact'),
url(_(r'login/?$'), LoginView.as_view(), name='login'),
url(_(r'signup/?$'), SignupView.as_view(), name='signup'),
url(r'^logout/?$', 'django.contrib.auth.views.logout',
{'next_page': '/digitalglarus/login?logged_out=true'}, name='logout'),
url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'),
url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
url(_(r'history/?$'), HistoryView.as_view(), name='history'),
url(_(r'booking/?$'), BookingSelectDatesView.as_view(), name='booking'),
url(_(r'booking/payment/?$'), BookingPaymentView.as_view(), name='booking_payment'),
url(_(r'membership/payment/?$'), MembershipPaymentView.as_view(), name='membership_payment'),
url(_(r'membership/activated/?$'), MembershipActivatedView.as_view(),
name='membership_activated'),
url(_(r'membership/pricing/?$'), MembershipPricingView.as_view(),
name='membership_pricing'),
url(_(r'supporters/?$'), views.supporters, name='supporters'),
url(r'calendar_api/(?P<month>\d+)/(?P<year>\d+)?$', views.CalendarApi.as_view(),name='calendar_api_1'),
url(r'calendar_api/', views.CalendarApi.as_view(),name='calendar_api'),

View file

@ -28,8 +28,11 @@ from utils.forms import PasswordResetRequestForm
from utils.stripe_utils import StripeUtils
from .forms import LoginForm, SignupForm, MembershipBillingForm
from .models import MembershipType, Membership, MembershipOrder
from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm,\
BookingBillingForm
from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\
BookingOrder
class IndexView(TemplateView):
@ -63,7 +66,7 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
class HistoryView(TemplateView):
template_name = "digitalglarus/history.html"
def get_context_data(self, **kwargs):
def get_context_data(self, *args, **kwargs):
context = super(HistoryView, self).get_context_data(**kwargs)
supporters = Supporter.objects.all()
context.update({
@ -72,9 +75,149 @@ class HistoryView(TemplateView):
return context
class BookingSelectDatesView(LoginRequiredMixin, FormView):
template_name = "digitalglarus/booking.html"
form_class = BookingDateForm
login_url = reverse_lazy('digitalglarus:login')
success_url = reverse_lazy('digitalglarus:booking_payment')
def form_valid(self, form):
user = self.request.user
start_date = form.cleaned_data.get('start_date')
end_date = form.cleaned_data.get('end_date')
booking_days = (end_date - start_date).days
original_price, discount_price, free_days = Booking.\
booking_price(user, start_date, end_date)
self.request.session.update({
'original_price': original_price,
'discount_price': discount_price,
'booking_days': booking_days,
'free_days': free_days,
'start_date': start_date.strftime('%m/%d/%Y'),
'end_date': end_date.strftime('%m/%d/%Y'),
})
return super(BookingSelectDatesView, self).form_valid(form)
class BookingPaymentView(LoginRequiredMixin, FormView):
template_name = "digitalglarus/booking_payment.html"
form_class = BookingBillingForm
success_url = reverse_lazy('digitalglarus:booking_payment')
booking_needed_fields = ['original_price', 'discount_price', 'booking_days', 'free_days',
'start_date', 'end_date']
def dispatch(self, request, *args, **kwargs):
from_booking = all(field in request.session.keys()
for field in self.booking_needed_fields)
if not from_booking:
return HttpResponseRedirect(reverse('digitalglarus:booking'))
return super(BookingPaymentView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
form_kwargs = super(BookingPaymentView, self).get_form_kwargs()
form_kwargs.update({
'initial': {
'start_date': self.request.session.get('start_date'),
'end_date': self.request.session.get('end_date'),
'price': self.request.session.get('discount_price'),
}
})
return form_kwargs
def get_context_data(self, *args, **kwargs):
context = super(BookingPaymentView, self).get_context_data(*args, **kwargs)
booking_data = {key: self.request.session.get(key)
for key in self.booking_needed_fields}
booking_price_per_day = BookingPrice.objects.get().price_per_day
total_discount = booking_price_per_day * booking_data.get('free_days')
booking_data.update({
'booking_price_per_day': booking_price_per_day,
'total_discount': total_discount,
'stripe_key': settings.STRIPE_API_PUBLIC_KEY
})
context.update(booking_data)
return context
def form_valid(self, form):
data = form.cleaned_data
context = self.get_context_data()
token = data.get('token')
start_date = data.get('start_date')
end_date = data.get('end_date')
original_price, discount_price, free_days = Booking.\
booking_price(self.request.user, start_date, end_date)
# Get or create stripe customer
customer = StripeCustomer.get_or_create(email=self.request.user.email,
token=token)
if not customer:
form.add_error("__all__", "Invalid credit card")
return self.render_to_response(self.get_context_data(form=form))
# Make stripe charge to a customer
stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=original_price,
customer=customer.stripe_id)
charge = charge_response.get('response_object')
# Check if the payment was approved
if not charge:
context.update({
'paymentError': charge_response.get('error'),
'form': form
})
return render(self.request, self.template_name, context)
charge = charge_response.get('response_object')
# Create Billing Address
billing_address = form.save()
# Create membership plan
booking_data = {
'start_date': start_date,
'end_date': end_date,
'start_date': start_date,
'free_days': free_days,
'price': discount_price,
}
booking = Booking.create(booking_data)
# Create membership order
order_data = {
'booking': booking,
'customer': customer,
'billing_address': billing_address,
'stripe_charge': charge
}
BookingOrder.create(order_data)
# request.session.update({
# 'membership_price': membership.type.first_month_price,
# 'membership_dates': membership.type.first_month_formated_range
# })
return super(BookingPaymentView, self).form_valid(form)
# return HttpResponseRedirect(reverse('digitalglarus:membership_activated'))
class MembershipPricingView(TemplateView):
template_name = "digitalglarus/membership_pricing.html"
def get_context_data(self, **kwargs):
context = super(MembershipPricingView, self).get_context_data(**kwargs)
membership_type = MembershipType.objects.last()
context.update({
'membership_type': membership_type
})
return context
class MembershipPaymentView(LoginRequiredMixin, FormView):
template_name = "digitalglarus/membership_payment.html"
login_url = reverse_lazy('digitalglarus:login')
login_url = reverse_lazy('digitalglarus:signup')
form_class = MembershipBillingForm
def get_form_kwargs(self):