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:
parent
1152e41b6e
commit
2eb2c90b1f
16 changed files with 783 additions and 25 deletions
|
@ -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
|
||||
|
|
|
@ -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()),
|
||||
],
|
||||
),
|
||||
]
|
19
digitalglarus/migrations/0010_auto_20160828_1745.py
Normal file
19
digitalglarus/migrations/0010_auto_20160828_1745.py
Normal 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',
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
),
|
||||
]
|
20
digitalglarus/migrations/0012_booking_free_days.py
Normal file
20
digitalglarus/migrations/0012_booking_free_days.py
Normal 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),
|
||||
),
|
||||
]
|
19
digitalglarus/migrations/0013_remove_booking_membership.py
Normal file
19
digitalglarus/migrations/0013_remove_booking_membership.py
Normal 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
25
digitalglarus/mixins.py
Normal 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
|
|
@ -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)
|
||||
|
|
22
digitalglarus/static/digitalglarus/js/booking.js
Normal file
22
digitalglarus/static/digitalglarus/js/booking.js
Normal 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');
|
||||
});
|
||||
|
||||
});
|
53
digitalglarus/templates/digitalglarus/booking.html
Normal file
53
digitalglarus/templates/digitalglarus/booking.html
Normal 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> </p>
|
||||
</span>
|
||||
</div>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
176
digitalglarus/templates/digitalglarus/booking_payment.html
Normal file
176
digitalglarus/templates/digitalglarus/booking_payment.html
Normal 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> </p>
|
||||
</span>
|
||||
</div>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- stripe key data -->
|
||||
{% if stripe_key %}
|
||||
<script type="text/javascript">
|
||||
(function () {window.stripeKey = "{{stripe_key}}";})();
|
||||
</script>
|
||||
|
||||
{%endif%}
|
||||
|
||||
{% endblock %}
|
|
@ -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> </p>
|
||||
</span>
|
||||
</div>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
|
@ -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'),
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue