From 1152e41b6ef2d222bce4d1c29048d14f500ebbf6 Mon Sep 17 00:00:00 2001 From: Levi <levinoelvm@gmail.com> Date: Mon, 22 Aug 2016 02:52:29 -0500 Subject: [PATCH 1/3] Fixed membership payment view issues, Membership creation after a stripe charge, Membership order creation after stripe charge, Added membership activated view, Added membership activated html, Fixing membership cost function, Added function to format membership date ranges, Added membership calculated dates for first month into membership_payment html and membership_activated html --- .../migrations/0008_auto_20160820_1959.py | 30 ++++++++++ digitalglarus/models.py | 58 +++++++++++++++++-- .../digitalglarus/membership_activated.html | 57 ++++++++++++++++++ .../digitalglarus/membership_payment.html | 38 +++++++----- digitalglarus/urls.py | 6 +- digitalglarus/views.py | 52 ++++++++++++++--- 6 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 digitalglarus/migrations/0008_auto_20160820_1959.py create mode 100644 digitalglarus/templates/digitalglarus/membership_activated.html diff --git a/digitalglarus/migrations/0008_auto_20160820_1959.py b/digitalglarus/migrations/0008_auto_20160820_1959.py new file mode 100644 index 00000000..d71350b5 --- /dev/null +++ b/digitalglarus/migrations/0008_auto_20160820_1959.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-20 19:59 +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', '0007_auto_20160820_0408'), + ] + + operations = [ + migrations.AddField( + model_name='membershiporder', + name='billing_address', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress'), + preserve_default=False, + ), + migrations.AddField( + model_name='membershiporder', + name='customer', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer'), + preserve_default=False, + ), + ] diff --git a/digitalglarus/models.py b/digitalglarus/models.py index 612e1c02..e5b3ab88 100644 --- a/digitalglarus/models.py +++ b/digitalglarus/models.py @@ -1,7 +1,14 @@ + +import calendar +from datetime import datetime, date, timedelta from django.db import models 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 membership.models import StripeCustomer +from utils.models import BillingAddress class MembershipType(models.Model): @@ -13,25 +20,69 @@ class MembershipType(models.Model): name = models.CharField(choices=MEMBERSHIP_TYPES, max_length=20) price = models.FloatField() + @cached_property + def days_left(self): + current_date = date.today() + _, days_in_month = calendar.monthrange(current_date.year, current_date.month) + pass_days = current_date.day + days_left = days_in_month - pass_days + 1 + return days_left + + @cached_property + def first_month_price(self): + current_date = date.today() + _, days_in_month = calendar.monthrange(current_date.year, current_date.month) + pass_days = current_date.day + days_left = days_in_month - pass_days + 1 + percentage = days_left / days_in_month + membership_price = self.price + final_price = membership_price * percentage + return final_price + + @cached_property + def first_month_range(self): + current_date = date.today() + _, days_in_month = calendar.monthrange(current_date.year, current_date.month) + pass_days = current_date.day + days_left = days_in_month - pass_days + end_date = current_date + timedelta(days=days_left) + return current_date, end_date + + @cached_property + def first_month_formated_range(self): + start_date, end_date = self.first_month_range + return "{} - {}".format(datetime.strftime(start_date, "%b, %d %Y"), + datetime.strftime(end_date, "%b, %d %Y")) + class Membership(models.Model): type = models.ForeignKey(MembershipType) @classmethod - def create(cls, data, user): + def create(cls, data): instance = cls.objects.create(**data) - instance.assign_permissions(user) return instance class MembershipOrder(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): + 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 + class Supporter(models.Model): name = models.CharField(max_length=200) @@ -44,9 +95,6 @@ class Supporter(models.Model): return reverse('dgSupporters_view', args=[self.pk]) - - - class DGGallery(models.Model): parent = models.ForeignKey('self', blank=True, null=True) name = models.CharField(max_length=30) diff --git a/digitalglarus/templates/digitalglarus/membership_activated.html b/digitalglarus/templates/digitalglarus/membership_activated.html new file mode 100644 index 00000000..891ea27f --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_activated.html @@ -0,0 +1,57 @@ +{% 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"> + + <!-- <span class="glyphicon glyphicon-user"></span> --> + <div class="payment-box"> + <h2 class="billing-head">Membership Activated</h2> + <hr class="greyline-long"> + <h2 class="membership-lead">Your Digital Glarus membership is successfully activated for the following date.</h2> + <div class="date-box"> + <h2 class="date-oneline">{{membership_dates}}</h2> + <h2 class="activation-lead">Now you can book your next coworking!</h2> + </div> + <!--<hr class="primary">--> + <div class="signup-form form-group row"> + + <div class="button-booking-box form-inline row"> + <button type="submit" class="btn btn-primary btn-blue">Go to Booking</button> + </div> + <div class="notice-box text-left"> + <p class="order-bottom-text">Your membership will be automatically renewed each month. For deactivating go to<a href=#>my page</a></p> + </div> + </div> + + </div> + <div class="col-xs-12 col-sm-3 col-lg-4 text-center wow fadeInDown"> </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> +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_payment.html b/digitalglarus/templates/digitalglarus/membership_payment.html index 0345bda0..bde934de 100644 --- a/digitalglarus/templates/digitalglarus/membership_payment.html +++ b/digitalglarus/templates/digitalglarus/membership_payment.html @@ -8,6 +8,16 @@ 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"> @@ -23,9 +33,13 @@ Additional day costs 15CHF per day. More than 17 days a month it will charge only 290CHF/month.</h2> + <h2 class="membership-lead"> + You will be charged on the first of the month until you + cancel your subscription. Previous charges won't be refunded. + </h2> <hr class="greyline-long"> <h2 class="billing-head">Member Name</h2> - <h2 class="member-name">Nico Schottelius</h2> + <h2 class="member-name">{{request.user.name}}</h2> <hr class="greyline-long"> <h2 class="billing-head">Billing Adress</h2> <div class="signup-form form-group row"> @@ -44,20 +58,14 @@ <div class="row"> <div class="col-xs-9 col-md-12"> <div class="form-group"> - <div class="input-group"> <input type="text" class="form-control" name="cardName" placeholder="Name on card" required autofocus data-stripe="name" /> - <span class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></span> - </div> </div> </div> </div> <div class="row"> <div class="col-xs-9 col-md-12"> <div class="form-group"> - <div class="input-group"> <input type="text" class="form-control" name="cardNumber" placeholder="Valid Card Number" required data-stripe="number" /> - <span class="input-group-addon"><i class="fa fa-credit-card"></i></span> - </div> </div> </div> </div> @@ -75,7 +83,7 @@ </div> </div> </div> - <div class="col-xs-4 col-md-6 pull-right nopadding"> + <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" /> @@ -84,7 +92,7 @@ </div> <div class="row"> <div class="col-xs-12"> - <button class="btn btn-success btn-lg btn-block" type="submit">Purchase membership</button> + <button class="btn btn-primary btn-lg btn-blck " type="submit">Purchase membership</button> </div> </div> <div class="row" style="display:none;"> @@ -115,25 +123,25 @@ <div class="order-box"> <h2 class="col-xs-6 order-item">Digital Glarus Membership</h2> <br> - <h2 class="col-xs-6 order-duration">valid 2016.09.08 - 2016.10.08</h2> + <h2 class="col-xs-6 order-duration">valid {{membership_type.first_month_formated_range}}</h2> + <br/> <h2 class="order-person">1 person</h2> - <h2 class="col-xs-6 payment-total">Today's Total</h2> - <h2 class="order-sum">0.00CHF</h2> + <h2 class="col-xs-6 payment-total">Today's Total for {{membership_type.days_left}} days </h2> + <h2 class="order-sum">{{membership_type.first_month_price|floatformat}}CHF</h2> <hr class="greyline"> <h2 class="col-xs-6 payment-total">Total</h2> - <h2 class="order-result">35CHF</h2> + <h2 class="order-result">{{membership_type.first_month_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"> - <button type="submit" class="btn btn-primary">Continue to Review</button> </div> <div class="button-box"> - <p class="order-bottom-text">You can checkout on the next page</p> </div> </div> </div> diff --git a/digitalglarus/urls.py b/digitalglarus/urls.py index 88e1ba75..e81bd8b5 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from . import views from .views import ContactView, IndexView, AboutView, HistoryView, LoginView, SignupView,\ - PasswordResetView, PasswordResetConfirmView, MembershipPaymentView + PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView # from membership.views import LoginRegistrationView urlpatterns = [ @@ -15,7 +15,9 @@ urlpatterns = [ 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'membership/payment?$'), MembershipPaymentView.as_view(), name='membership_payment'), + url(_(r'membership/payment/?$'), MembershipPaymentView.as_view(), name='membership_payment'), + url(_(r'membership/activated/?$'), MembershipActivatedView.as_view(), + name='membership_activated'), 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'), diff --git a/digitalglarus/views.py b/digitalglarus/views.py index 00d3f87e..d0edcd05 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -13,7 +13,7 @@ from django.utils.translation import get_language from djangocms_blog.models import Post from django.contrib import messages from django.http import JsonResponse -from django.views.generic import View +from django.views.generic import View, DetailView from .models import Supporter from utils.forms import ContactUsForm @@ -29,7 +29,7 @@ from utils.stripe_utils import StripeUtils from .forms import LoginForm, SignupForm, MembershipBillingForm -from .models import MembershipType +from .models import MembershipType, Membership, MembershipOrder class IndexView(TemplateView): @@ -78,22 +78,24 @@ class MembershipPaymentView(LoginRequiredMixin, FormView): form_class = MembershipBillingForm def get_form_kwargs(self): - membership_type = MembershipType.objects.get(name='standard') + self.membership_type = MembershipType.objects.get(name='standard') form_kwargs = super(MembershipPaymentView, self).get_form_kwargs() form_kwargs.update({ - 'initial': {'membership_type': membership_type.id} + 'initial': { + 'membership_type': self.membership_type.id + } }) return form_kwargs def get_context_data(self, **kwargs): context = super(MembershipPaymentView, self).get_context_data(**kwargs) context.update({ - 'stripe_key': settings.STRIPE_API_PUBLIC_KEY + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, + 'membership_type': self.membership_type }) return context def post(self, request, *args, **kwargs): - import pdb;pdb.set_trace() form = self.get_form() if form.is_valid(): @@ -111,7 +113,7 @@ class MembershipPaymentView(LoginRequiredMixin, FormView): # Make stripe charge to a customer stripe_utils = StripeUtils() - charge_response = stripe_utils.make_charge(amount=membership_type.price, + charge_response = stripe_utils.make_charge(amount=membership_type.first_month_price, customer=customer.stripe_id) charge = charge_response.get('response_object') @@ -124,10 +126,46 @@ class MembershipPaymentView(LoginRequiredMixin, FormView): return render(request, self.template_name, context) charge = charge_response.get('response_object') + + # Create Billing Address + billing_address = form.save() + + # Create membership plan + membership_data = {'type': membership_type} + membership = Membership.create(membership_data) + + # Create membership order + order_data = { + 'membership': membership, + 'customer': customer, + 'billing_address': billing_address, + 'stripe_charge': charge + } + MembershipOrder.create(order_data) + + request.session.update({ + 'membership_price': membership.type.first_month_price, + 'membership_dates': membership.type.first_month_formated_range + }) + return HttpResponseRedirect(reverse('digitalglarus:membership_activated')) + else: return self.form_invalid(form) +class MembershipActivatedView(TemplateView): + template_name = "digitalglarus/membership_activated.html" + + def get_context_data(self, **kwargs): + context = super(MembershipActivatedView, self).get_context_data(**kwargs) + membership_price = self.request.session.get('membership_price') + membership_dates = self.request.session.get('membership_dates') + context.update({ + 'membership_price': membership_price, + 'membership_dates': membership_dates, + }) + return context + ############## OLD VIEWS class CalendarApi(View): From 2eb2c90b1fbf53601bce9e8605624c46c5eaebf2 Mon Sep 17 00:00:00 2001 From: Levi <levinoelvm@gmail.com> Date: Tue, 30 Aug 2016 21:32:45 -0500 Subject: [PATCH 2/3] 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. --- digitalglarus/forms.py | 45 ++++- ...0009_booking_bookingorder_bookingprices.py | 52 ++++++ .../migrations/0010_auto_20160828_1745.py | 19 ++ .../0011_bookingprice_special_price_offer.py | 21 +++ .../migrations/0012_booking_free_days.py | 20 ++ .../0013_remove_booking_membership.py | 19 ++ digitalglarus/mixins.py | 25 +++ digitalglarus/models.py | 78 +++++++- .../static/digitalglarus/js/booking.js | 22 +++ .../templates/digitalglarus/booking.html | 53 ++++++ .../digitalglarus/booking_payment.html | 176 ++++++++++++++++++ .../digitalglarus/membership_pricing.html | 87 +++++++++ .../templates/digitalglarus/signup.html | 2 +- digitalglarus/templates/new_base_glarus.html | 29 ++- digitalglarus/urls.py | 9 +- digitalglarus/views.py | 151 ++++++++++++++- 16 files changed, 783 insertions(+), 25 deletions(-) create mode 100644 digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py create mode 100644 digitalglarus/migrations/0010_auto_20160828_1745.py create mode 100644 digitalglarus/migrations/0011_bookingprice_special_price_offer.py create mode 100644 digitalglarus/migrations/0012_booking_free_days.py create mode 100644 digitalglarus/migrations/0013_remove_booking_membership.py create mode 100644 digitalglarus/mixins.py create mode 100644 digitalglarus/static/digitalglarus/js/booking.js create mode 100644 digitalglarus/templates/digitalglarus/booking.html create mode 100644 digitalglarus/templates/digitalglarus/booking_payment.html create mode 100644 digitalglarus/templates/digitalglarus/membership_pricing.html diff --git a/digitalglarus/forms.py b/digitalglarus/forms.py index 10f73b31..03a5e6cd 100644 --- a/digitalglarus/forms.py +++ b/digitalglarus/forms.py @@ -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 diff --git a/digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py b/digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py new file mode 100644 index 00000000..784c1f54 --- /dev/null +++ b/digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py @@ -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()), + ], + ), + ] diff --git a/digitalglarus/migrations/0010_auto_20160828_1745.py b/digitalglarus/migrations/0010_auto_20160828_1745.py new file mode 100644 index 00000000..b526562f --- /dev/null +++ b/digitalglarus/migrations/0010_auto_20160828_1745.py @@ -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', + ), + ] diff --git a/digitalglarus/migrations/0011_bookingprice_special_price_offer.py b/digitalglarus/migrations/0011_bookingprice_special_price_offer.py new file mode 100644 index 00000000..08a75e39 --- /dev/null +++ b/digitalglarus/migrations/0011_bookingprice_special_price_offer.py @@ -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, + ), + ] diff --git a/digitalglarus/migrations/0012_booking_free_days.py b/digitalglarus/migrations/0012_booking_free_days.py new file mode 100644 index 00000000..17d95319 --- /dev/null +++ b/digitalglarus/migrations/0012_booking_free_days.py @@ -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), + ), + ] diff --git a/digitalglarus/migrations/0013_remove_booking_membership.py b/digitalglarus/migrations/0013_remove_booking_membership.py new file mode 100644 index 00000000..7b2ffa24 --- /dev/null +++ b/digitalglarus/migrations/0013_remove_booking_membership.py @@ -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', + ), + ] diff --git a/digitalglarus/mixins.py b/digitalglarus/mixins.py new file mode 100644 index 00000000..6d555f24 --- /dev/null +++ b/digitalglarus/mixins.py @@ -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 diff --git a/digitalglarus/models.py b/digitalglarus/models.py index e5b3ab88..e229fabd 100644 --- a/digitalglarus/models.py +++ b/digitalglarus/models.py @@ -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) diff --git a/digitalglarus/static/digitalglarus/js/booking.js b/digitalglarus/static/digitalglarus/js/booking.js new file mode 100644 index 00000000..ed1929d4 --- /dev/null +++ b/digitalglarus/static/digitalglarus/js/booking.js @@ -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'); + }); + +}); \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking.html b/digitalglarus/templates/digitalglarus/booking.html new file mode 100644 index 00000000..6a8d4374 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking.html @@ -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 %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_payment.html b/digitalglarus/templates/digitalglarus/booking_payment.html new file mode 100644 index 00000000..7faee9c9 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_payment.html @@ -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 %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_pricing.html b/digitalglarus/templates/digitalglarus/membership_pricing.html new file mode 100644 index 00000000..fec32399 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_pricing.html @@ -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 %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/signup.html b/digitalglarus/templates/digitalglarus/signup.html index 118057f8..aea83f4e 100644 --- a/digitalglarus/templates/digitalglarus/signup.html +++ b/digitalglarus/templates/digitalglarus/signup.html @@ -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 %} diff --git a/digitalglarus/templates/new_base_glarus.html b/digitalglarus/templates/new_base_glarus.html index 74bcaf1d..b7223997 100644 --- a/digitalglarus/templates/new_base_glarus.html +++ b/digitalglarus/templates/new_base_glarus.html @@ -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> \ No newline at end of file diff --git a/digitalglarus/urls.py b/digitalglarus/urls.py index e81bd8b5..45884468 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -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'), diff --git a/digitalglarus/views.py b/digitalglarus/views.py index d0edcd05..479eadfc 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -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): From e3d1761d455874d4d3867421373d0ccf5414e674 Mon Sep 17 00:00:00 2001 From: Levi <levinoelvm@gmail.com> Date: Mon, 5 Sep 2016 19:45:45 -0500 Subject: [PATCH 3/3] Now booking is only for users with memberships. Added method to know if a membership is active or not. Added new formula for booking pricing. handling border cases when an user want to book. Added booking order detail view. Added booking order detail html. --- digitalglarus/forms.py | 2 +- .../migrations/0014_booking_final_price.py | 21 +++++ digitalglarus/mixins.py | 13 +++ digitalglarus/models.py | 67 ++++++++------ .../static/digitalglarus/js/booking.js | 7 +- .../static/digitalglarus/js/utils.js | 21 +++++ .../digitalglarus/booking_orders_detail.html | 88 +++++++++++++++++++ .../digitalglarus/booking_orders_list.html | 68 ++++++++++++++ .../digitalglarus/booking_payment.html | 12 ++- .../digitalglarus/membership_activated.html | 2 +- digitalglarus/templates/new_base_glarus.html | 3 + digitalglarus/urls.py | 7 +- digitalglarus/views.py | 76 +++++++++++++--- membership/models.py | 3 + 14 files changed, 345 insertions(+), 45 deletions(-) create mode 100644 digitalglarus/migrations/0014_booking_final_price.py create mode 100644 digitalglarus/static/digitalglarus/js/utils.js create mode 100644 digitalglarus/templates/digitalglarus/booking_orders_detail.html create mode 100644 digitalglarus/templates/digitalglarus/booking_orders_list.html diff --git a/digitalglarus/forms.py b/digitalglarus/forms.py index 03a5e6cd..db3aab94 100644 --- a/digitalglarus/forms.py +++ b/digitalglarus/forms.py @@ -68,7 +68,7 @@ class BookingDateForm(forms.Form): except ValueError: raise forms.ValidationError("Submit valid dates.") - if start_date >= end_date: + if start_date > end_date: raise forms.ValidationError("Your end date must be greather than your start date.") return start_date, end_date diff --git a/digitalglarus/migrations/0014_booking_final_price.py b/digitalglarus/migrations/0014_booking_final_price.py new file mode 100644 index 00000000..f417b714 --- /dev/null +++ b/digitalglarus/migrations/0014_booking_final_price.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-02 03:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0013_remove_booking_membership'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='final_price', + field=models.FloatField(default=250), + preserve_default=False, + ), + ] diff --git a/digitalglarus/mixins.py b/digitalglarus/mixins.py index 6d555f24..13329f73 100644 --- a/digitalglarus/mixins.py +++ b/digitalglarus/mixins.py @@ -1,8 +1,20 @@ from django.db import models +from django.http import HttpResponseRedirect from membership.models import StripeCustomer from utils.models import BillingAddress +class MembershipRequired(object): + membership_redirect_url = None + + def dispatch(self, request, *args, **kwargs): + from .models import Membership + if not Membership.is_digitalglarus_member(request.user): + return HttpResponseRedirect(self.membership_redirect_url) + + return super(MembershipRequired, self).dispatch(request, *args, **kwargs) + + class Ordereable(models.Model): customer = models.ForeignKey(StripeCustomer) billing_address = models.ForeignKey(BillingAddress) @@ -22,4 +34,5 @@ class Ordereable(models.Model): instance.stripe_charge_id = stripe_charge.id instance.last4 = stripe_charge.source.last4 instance.cc_brand = stripe_charge.source.brand + instance.save() return instance diff --git a/digitalglarus/models.py b/digitalglarus/models.py index e229fabd..49d1327f 100644 --- a/digitalglarus/models.py +++ b/digitalglarus/models.py @@ -1,7 +1,9 @@ import calendar from datetime import datetime, date, timedelta +from dateutil.relativedelta import relativedelta from django.db import models +from django.db.models import Q from cms.models import CMSPlugin from filer.fields.image import FilerImageField from django.core.urlresolvers import reverse @@ -59,6 +61,14 @@ class MembershipType(models.Model): class Membership(models.Model): type = models.ForeignKey(MembershipType) + @classmethod + def is_digitalglarus_member(cls, user): + past_month = (datetime.today() - relativedelta(months=1)).month + has_booking_current_month = Q(membershiporder__created_at__month=datetime.today().month) + has_booking_past_month = Q(membershiporder__customer__user=user, + membershiporder__created_at__month=past_month) + return cls.objects.filter(has_booking_past_month | has_booking_current_month).exists() + @classmethod def create(cls, data): instance = cls.objects.create(**data) @@ -88,6 +98,7 @@ class Booking(models.Model): end_date = models.DateField() price = models.FloatField() free_days = models.IntegerField(default=0) + final_price = models.FloatField() @classmethod def create(cls, data): @@ -95,52 +106,50 @@ class Booking(models.Model): return instance @classmethod - def get_ramaining_free_days(cls, user): - # ZERO_DAYS = 0 - # ONE_DAY = 1 + def get_ramaining_free_days(cls, user, start_date, end_date): + TWO_DAYS = 2 + start_month = start_date.month + end_month = end_date.month + months = abs(start_month - (end_month + 12) if end_month < start_month + else end_month - start_month) 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 + free_days_this_month = TWO_DAYS - sum(map(lambda x: x.free_days, current_month_bookings)) + total_free_days = months * TWO_DAYS + free_days_this_month + return total_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 - """ + + MAX_MONTH_PRICE = 290 + MAX_MONTH_DAYS_PROMOTION = 31 + MIN_MONTH_DAYS_PROMOTION = 19 + booking_prices = BookingPrice.objects.last() price_per_day = booking_prices.price_per_day - booking_days = (end_date - start_date).days + booking_days = (end_date - start_date).days + 1 + months = booking_days // MAX_MONTH_DAYS_PROMOTION + remanent_days = booking_days % MAX_MONTH_DAYS_PROMOTION + months_prices = months * MAX_MONTH_PRICE + remanent_days_price = remanent_days * price_per_day \ + if remanent_days <= MIN_MONTH_DAYS_PROMOTION else MAX_MONTH_PRICE + normal_price = months_prices + remanent_days_price - 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 + free_days = cls.get_ramaining_free_days(user, start_date, end_date) + final_booking_price = normal_price - (free_days * price_per_day) - return original_booking_price, final_booking_price, free_days + return normal_price, final_booking_price, free_days class BookingOrder(Ordereable, models.Model): booking = models.OneToOneField(Booking) + def booking_days(self): + return (self.booking.end_date - self.booking.start_date).days + 1 + class Supporter(models.Model): name = models.CharField(max_length=200) diff --git a/digitalglarus/static/digitalglarus/js/booking.js b/digitalglarus/static/digitalglarus/js/booking.js index ed1929d4..0cb97667 100644 --- a/digitalglarus/static/digitalglarus/js/booking.js +++ b/digitalglarus/static/digitalglarus/js/booking.js @@ -1,13 +1,16 @@ $( document ).ready(function() { + // $('#booking-date-range').daterangepicker(); - + var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); + // var tomorrow = today.setDate(today.getDate() + 1); $('#booking-date-range').daterangepicker({ autoUpdateInput: false, locale: { cancelLabel: 'Clear' - } + }, + minDate: tomorrow, }); diff --git a/digitalglarus/static/digitalglarus/js/utils.js b/digitalglarus/static/digitalglarus/js/utils.js new file mode 100644 index 00000000..d780fc25 --- /dev/null +++ b/digitalglarus/static/digitalglarus/js/utils.js @@ -0,0 +1,21 @@ +$( document ).ready(function() { + + + function printDiv(divName) { + var printContents = document.getElementById(divName).innerHTML; + var originalContents = document.body.innerHTML; + + document.body.innerHTML = printContents; + + window.print(); + + document.body.innerHTML = originalContents; + } + + + $('.print').on('click', function(e){ + printDiv(e.target.getAttribute("data-print") ); + + }); + +}); \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_orders_detail.html b/digitalglarus/templates/digitalglarus/booking_orders_detail.html new file mode 100644 index 00000000..6dcb604a --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_orders_detail.html @@ -0,0 +1,88 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + <style type="text/css"> + + .invoice-title{ + text-align: center !important; + } + </style> + +<script type="text/javascript"> + +</script> + + <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">Your Booking Detail</h2> + <hr class="greyline-long"> + <h2 class="billing-head">Invoice<btn class="btn btn-primary btn-grey btn-edit print" data-print="price">Get PDF</btn></h2> + + <h2 class="order-head">Order Number</h2> + <h2 class="member-name">#{{order.id}}</h2> + + <h2 class="order-head">Billed to :</h2> + <h2 class="history-name">{{user.name}}<br> + {{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br> + {{order.billing_address.city}}, {{order.billing_address.country}}. + </h2> + + <h2 class="order-head">Payment Method</h2> + <h2 class="history-name"> + {{order.cc_brand}} ending **** {{order.last4}}<br> + {{user.email}} + </h2> + + <hr class="greyline-long"> + <h2 class="order-head">Order Summary</h2> + <h2 class="history-name"> + Dates: {{start_date}} - {{end_date}}<br> + </h2> + <h2 class="col-xs-6 payment-total text-left">Total days {{booking_days}}</h2> + <h2 class="order-sum">{{original_price|floatformat}}CHF</h2> + {% if free_days %} + <h2 class="col-xs-6 payment-total text-left">Free days {{free_days}} </h2> + <h2 class="order-sum">-{{total_discount|floatformat}}CHF</h2> + {% endif %} + <hr class="greyline-long"> + <h2 class="col-xs-6 payment-total text-left"> Total</h2> + <h2 class="order-result">{{final_price|floatformat}}CHF</h2> + <br> + </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">Order Summary</h2> + </div> + <div class="order-box"> + <h2 class="col-xs-6 order-item" style="padding-bottom:10px">Dates: {{start_date}} - {{end_date}}<br></h2> + + <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">-{{total_discount|floatformat}}CHF</h2> + {% endif %} + <hr class="greyline"> + <h2 class="col-xs-6 payment-total">Total</h2> + <h2 class="order-result">{{final_price|floatformat}}CHF</h2> + </div> + + </div> + </div> + </section> + +<!-- stripe key data --> +{% if stripe_key %} +<script type="text/javascript"> + (function () {window.stripeKey = "{{stripe_key}}";})(); +</script> + +{%endif%} + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_orders_list.html b/digitalglarus/templates/digitalglarus/booking_orders_list.html new file mode 100644 index 00000000..58a5e4bd --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_orders_list.html @@ -0,0 +1,68 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + <!-- Header --> + <!-- Services Section --> + <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">Your Order History</h2> + <hr class="greyline-long"> + <h2 class="order-head">Member Name</h2> + <h2 class="member-name">{{request.user.name}}</h2> + <hr class="greyline-long"> + <h2 class="order-head">Active Membership</h2> + <h2 class="member-name">2016.11.13-2016.12.13</h2> + <hr class="greyline-long"> + <h2 class="order-head">Booking history</h2> + <table class="table"> + <thead> + <tr> + <th>#</th> + <th>Booking dates</th> + <th>Days</th> + <th>Invoice</th> + </tr> + </thead> + <tbody> + {% for order in orders%} + <tr> + <th scope="row">{{order.id}}</th> + <td>{{order.booking.start_date}}-{{order.booking.end_date}}</td> + <td>{{order.booking_days}}</td> + <td><a class="btn btn-xs btn-primary btn-darkgrey" href="{% url 'digitalglarus:booking_orders_detail' order.id %}">View</a></td> + </tr> + {% endfor %} + </tbody> + </table> + + <h2 class="order-head">Billing Adress<btn class="btn btn-primary btn-grey btn-edit">Edit</btn></h2> + <h2 class="history-name">Nico Schottelius<br> + In der Au 7 8762 Schwanden<br> + Switzerland + </h2> + + </div> + </div> + + + <div class="col-xs-12 col-sm-4 col-lg-4 wow fadeInDown"> + <div class="order-summary"> + <h2 class="thankyou">Thank You!</h2> + <div class="order-box"> + <span class="glyphicon glyphicon-heart icon-up"></span> + <h2 class="signup-lead text-center">Digital Glarus lives with your love!<br> + Our coworking space is here because of your love and support.</h2> + + <hr class="greyline"> + + <p class="order-bottom-text text-center">This box is here just to thank you</p> + </div> + + </div> + </div> + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_payment.html b/digitalglarus/templates/digitalglarus/booking_payment.html index 7faee9c9..ff639a92 100644 --- a/digitalglarus/templates/digitalglarus/booking_payment.html +++ b/digitalglarus/templates/digitalglarus/booking_payment.html @@ -27,7 +27,17 @@ <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> + <h2 class="billing-head">Billing Adress</h2> + <h2 class="membership-lead"> + Your Digital Glarus Membership enables + you to use our coworking space and it includes + 2 working days for the month you signed up. + The membership fee is a monthly subscription. + Additional day costs + 15CHF per day. More than 17 days a month it + will charge only 290CHF/month. + </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 %} diff --git a/digitalglarus/templates/digitalglarus/membership_activated.html b/digitalglarus/templates/digitalglarus/membership_activated.html index 891ea27f..f899f6ec 100644 --- a/digitalglarus/templates/digitalglarus/membership_activated.html +++ b/digitalglarus/templates/digitalglarus/membership_activated.html @@ -21,7 +21,7 @@ <div class="signup-form form-group row"> <div class="button-booking-box form-inline row"> - <button type="submit" class="btn btn-primary btn-blue">Go to Booking</button> + <a class="btn btn-primary btn-blue" href={% url 'digitalglarus:booking' %}>Go to Booking</a> </div> <div class="notice-box text-left"> <p class="order-bottom-text">Your membership will be automatically renewed each month. For deactivating go to<a href=#>my page</a></p> diff --git a/digitalglarus/templates/new_base_glarus.html b/digitalglarus/templates/new_base_glarus.html index b7223997..7d5f9167 100644 --- a/digitalglarus/templates/new_base_glarus.html +++ b/digitalglarus/templates/new_base_glarus.html @@ -180,6 +180,9 @@ <!-- Booking JavaScript --> <script src="{% static 'digitalglarus/js/booking.js' %}"></script> +<!-- Utils JavaScript --> +<script src="{% static 'digitalglarus/js/utils.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"> diff --git a/digitalglarus/urls.py b/digitalglarus/urls.py index 45884468..63560e61 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -4,7 +4,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,\ - MembershipPricingView, BookingSelectDatesView, BookingPaymentView + MembershipPricingView, BookingSelectDatesView, BookingPaymentView, OrdersBookingDetailView,\ + BookingOrdersListView # from membership.views import LoginRegistrationView urlpatterns = [ @@ -20,6 +21,10 @@ urlpatterns = [ 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'booking/orders/(?P<pk>\d+)/?$'), OrdersBookingDetailView.as_view(), + name='booking_orders_detail'), + url(_(r'booking/orders/?$'), BookingOrdersListView.as_view(), + name='booking_orders_list'), url(_(r'membership/payment/?$'), MembershipPaymentView.as_view(), name='membership_payment'), url(_(r'membership/activated/?$'), MembershipActivatedView.as_view(), name='membership_activated'), diff --git a/digitalglarus/views.py b/digitalglarus/views.py index 479eadfc..dadc8abe 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -13,13 +13,13 @@ from django.utils.translation import get_language from djangocms_blog.models import Post from django.contrib import messages from django.http import JsonResponse -from django.views.generic import View, DetailView +from django.views.generic import View, DetailView, ListView from .models import Supporter from utils.forms import ContactUsForm from django.views.generic.edit import FormView from membership.calendar.calendar import BookCalendar -from membership.models import Calendar as CalendarModel, CustomUser, StripeCustomer +from membership.models import Calendar as CalendarModel, StripeCustomer from utils.views import LoginViewMixin, SignupViewMixin, \ @@ -34,6 +34,8 @@ from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\ BookingOrder +from .mixins import MembershipRequired + class IndexView(TemplateView): template_name = "digitalglarus/old_index.html" @@ -75,9 +77,10 @@ class HistoryView(TemplateView): return context -class BookingSelectDatesView(LoginRequiredMixin, FormView): +class BookingSelectDatesView(LoginRequiredMixin, MembershipRequired, FormView): template_name = "digitalglarus/booking.html" form_class = BookingDateForm + membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing') login_url = reverse_lazy('digitalglarus:login') success_url = reverse_lazy('digitalglarus:booking_payment') @@ -85,7 +88,7 @@ class BookingSelectDatesView(LoginRequiredMixin, FormView): 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 + booking_days = (end_date - start_date).days + 1 original_price, discount_price, free_days = Booking.\ booking_price(user, start_date, end_date) self.request.session.update({ @@ -99,10 +102,11 @@ class BookingSelectDatesView(LoginRequiredMixin, FormView): return super(BookingSelectDatesView, self).form_valid(form) -class BookingPaymentView(LoginRequiredMixin, FormView): +class BookingPaymentView(LoginRequiredMixin, MembershipRequired, FormView): template_name = "digitalglarus/booking_payment.html" form_class = BookingBillingForm - success_url = reverse_lazy('digitalglarus:booking_payment') + membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing') + # success_url = reverse_lazy('digitalglarus:booking_payment') booking_needed_fields = ['original_price', 'discount_price', 'booking_days', 'free_days', 'start_date', 'end_date'] @@ -114,6 +118,9 @@ class BookingPaymentView(LoginRequiredMixin, FormView): return super(BookingPaymentView, self).dispatch(request, *args, **kwargs) + def get_success_url(self, order_id): + return reverse('digitalglarus:booking_orders_datail', kwargs={'pk': order_id}) + def get_form_kwargs(self): form_kwargs = super(BookingPaymentView, self).get_form_kwargs() form_kwargs.update({ @@ -147,7 +154,7 @@ class BookingPaymentView(LoginRequiredMixin, FormView): start_date = data.get('start_date') end_date = data.get('end_date') - original_price, discount_price, free_days = Booking.\ + normal_price, final_price, free_days = Booking.\ booking_price(self.request.user, start_date, end_date) # Get or create stripe customer @@ -159,7 +166,7 @@ class BookingPaymentView(LoginRequiredMixin, FormView): # Make stripe charge to a customer stripe_utils = StripeUtils() - charge_response = stripe_utils.make_charge(amount=original_price, + charge_response = stripe_utils.make_charge(amount=final_price, customer=customer.stripe_id) charge = charge_response.get('response_object') @@ -182,7 +189,8 @@ class BookingPaymentView(LoginRequiredMixin, FormView): 'end_date': end_date, 'start_date': start_date, 'free_days': free_days, - 'price': discount_price, + 'price': normal_price, + 'final_price': final_price, } booking = Booking.create(booking_data) @@ -193,12 +201,13 @@ class BookingPaymentView(LoginRequiredMixin, FormView): 'billing_address': billing_address, 'stripe_charge': charge } - BookingOrder.create(order_data) + order = BookingOrder.create(order_data) # request.session.update({ # 'membership_price': membership.type.first_month_price, # 'membership_dates': membership.type.first_month_formated_range # }) + return HttpResponseRedirect(self.get_success_url(order.id)) return super(BookingPaymentView, self).form_valid(form) # return HttpResponseRedirect(reverse('digitalglarus:membership_activated')) @@ -310,6 +319,53 @@ class MembershipActivatedView(TemplateView): return context +class OrdersBookingDetailView(LoginRequiredMixin, DetailView): + template_name = "digitalglarus/booking_orders_detail.html" + context_object_name = "order" + login_url = reverse_lazy('digitalglarus:login') + # permission_required = ['view_hostingorder'] + model = BookingOrder + + def get_context_data(self, *args, **kwargs): + + context = super(OrdersBookingDetailView, self).get_context_data(**kwargs) + + bookig_order = self.object + booking = bookig_order.booking + + start_date = booking.start_date + end_date = booking.end_date + free_days = booking.free_days + + booking_days = (end_date - start_date).days + 1 + original_price = booking.price + final_price = booking.final_price + context.update({ + 'original_price': original_price, + 'total_discount': original_price - final_price, + 'final_price': final_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 context + + +class BookingOrdersListView(LoginRequiredMixin, ListView): + template_name = "digitalglarus/booking_orders_list.html" + context_object_name = "orders" + login_url = reverse_lazy('digitalglarus:login') + model = BookingOrder + paginate_by = 10 + + def get_queryset(self): + queryset = super(BookingOrdersListView, self).get_queryset() + queryset = queryset.filter(customer__user=self.request.user) + return queryset + + ############## OLD VIEWS class CalendarApi(View): def get(self,request,month,year): diff --git a/membership/models.py b/membership/models.py index 7672a7b4..88e3e902 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,5 +1,8 @@ from datetime import datetime + + + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User, AbstractBaseUser, BaseUserManager, AbstractUser, PermissionsMixin