Fixed cc brand bug, Added button to print/save pdf to booking order detail view.Added booking order list.Added booking order list views. Added customised user navbar. Added “my bookings” link to navbar. Added membership active date range. Fixed function to calculate if a membership is active or not. Added membership order detail view. Added order membership view. Create order membership detail html.Added validation to not allow booking on already booked dates.

This commit is contained in:
Levi 2016-09-08 23:24:52 -05:00
parent 63dd0f3ea0
commit d917c8a606
12 changed files with 397 additions and 33 deletions

View file

@ -1,5 +1,7 @@
from django.contrib import admin
from .models import Supporter, DGGallery, DGPicture
from .models import Supporter, DGGallery, DGPicture, Booking, BookingPrice,\
MembershipOrder, Membership, MembershipType, BookingOrder
from utils.models import ContactMessage
#
class DGPictureInline(admin.StackedInline):
@ -10,4 +12,9 @@ class DGGalleryAdmin(admin.ModelAdmin):
admin.site.register(DGGallery, DGGalleryAdmin)
admin.site.register(ContactMessage)
admin.site.register(Supporter)
admin.site.register(Booking)
admin.site.register(BookingPrice)
admin.site.register(MembershipOrder)
admin.site.register(Membership)
admin.site.register(MembershipType)
admin.site.register(BookingOrder)

View file

@ -1,11 +1,14 @@
from django import forms
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from datetime import datetime
from utils.models import BillingAddress
from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm
from .models import MembershipType
from .models import Booking
class LoginForm(LoginFormMixin):
@ -55,9 +58,10 @@ class BookingBillingForm(BillingAddressForm):
class BookingDateForm(forms.Form):
start_date = forms.DateField(required=False)
end_date = forms.DateField(required=False)
date_range = forms.CharField(required=False)
start_date = forms.DateField(required=False, widget=forms.HiddenInput())
end_date = forms.DateField(required=False, widget=forms.HiddenInput())
date_range = forms.CharField(required=False,
widget=forms.TextInput(attrs={'id': 'booking-date-range'}))
def clean_date_range(self):
date_range = self.cleaned_data.get('date_range')
@ -70,9 +74,18 @@ class BookingDateForm(forms.Form):
if start_date > end_date:
raise forms.ValidationError("Your end date must be greather than your start date.")
q1 = Q(start_date__lte=start_date, end_date__gte=start_date)
q2 = Q(start_date__gt=start_date, start_date__lte=end_date)
if Booking.objects.filter(q1 | q2).exists():
raise forms.ValidationError("You already have a booking in these dates.")
return start_date, end_date
def clean(self):
# import pdb
# pdb.set_trace()
if self.cleaned_data.get('date_range'):
start_date, end_date = self.cleaned_data.get('date_range')
self.cleaned_data['start_date'] = start_date
self.cleaned_data['end_date'] = end_date

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-09-08 01:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0014_booking_final_price'),
]
operations = [
migrations.AddField(
model_name='bookingorder',
name='amount',
field=models.FloatField(default=35),
preserve_default=False,
),
migrations.AddField(
model_name='membershiporder',
name='amount',
field=models.FloatField(default=35.0),
preserve_default=False,
),
]

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-09-09 01:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0015_auto_20160908_0149'),
]
operations = [
migrations.RenameField(
model_name='bookingprice',
old_name='special_price_offer',
new_name='special_month_price',
),
migrations.AddField(
model_name='bookingorder',
name='original_price',
field=models.FloatField(default=20),
preserve_default=False,
),
migrations.AddField(
model_name='bookingorder',
name='special_month_price',
field=models.FloatField(default=290),
preserve_default=False,
),
]

View file

@ -4,7 +4,7 @@ from membership.models import StripeCustomer
from utils.models import BillingAddress
class MembershipRequired(object):
class MembershipRequiredMixin(object):
membership_redirect_url = None
def dispatch(self, request, *args, **kwargs):
@ -12,11 +12,23 @@ class MembershipRequired(object):
if not Membership.is_digitalglarus_member(request.user):
return HttpResponseRedirect(self.membership_redirect_url)
return super(MembershipRequired, self).dispatch(request, *args, **kwargs)
return super(MembershipRequiredMixin, self).dispatch(request, *args, **kwargs)
class IsNotMemberMixin(object):
already_member_redirect_url = None
def dispatch(self, request, *args, **kwargs):
from .models import Membership
if Membership.is_digitalglarus_member(request.user):
return HttpResponseRedirect(self.already_member_redirect_url)
return super(MembershipRequiredMixin, self).dispatch(request, *args, **kwargs)
class Ordereable(models.Model):
customer = models.ForeignKey(StripeCustomer)
amount = models.FloatField()
billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)

View file

@ -64,7 +64,8 @@ class Membership(models.Model):
@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_current_month = Q(membershiporder__customer__user=user,
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()
@ -78,6 +79,25 @@ class Membership(models.Model):
class MembershipOrder(Ordereable, models.Model):
membership = models.ForeignKey(Membership)
@classmethod
def current_membership(cls, user):
last_payment = cls.objects.\
filter(customer__user=user).last()
start_date = last_payment.created_at
_, days_in_month = calendar.monthrange(start_date.year,
start_date.month)
start_date.replace(day=1)
end_date = start_date + timedelta(days=days_in_month)
return start_date, end_date
def get_membership_range_date(self):
start_date = self.created_at
_, days_in_month = calendar.monthrange(start_date.year,
start_date.month)
start_date.replace(day=1)
end_date = start_date + timedelta(days=days_in_month)
return start_date, end_date
@classmethod
def create(cls, data):
stripe_charge = data.pop('stripe_charge', None)
@ -85,12 +105,13 @@ class MembershipOrder(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
class BookingPrice(models.Model):
price_per_day = models.FloatField()
special_price_offer = models.FloatField()
special_month_price = models.FloatField()
class Booking(models.Model):
@ -124,7 +145,7 @@ class Booking(models.Model):
@classmethod
def booking_price(cls, user, start_date, end_date):
MAX_MONTH_PRICE = 290
MAX_MONTH_PRICE = BookingPrice.objects.last().special_month_price
MAX_MONTH_DAYS_PROMOTION = 31
MIN_MONTH_DAYS_PROMOTION = 19
@ -146,6 +167,8 @@ class Booking(models.Model):
class BookingOrder(Ordereable, models.Model):
booking = models.OneToOneField(Booking)
original_price = models.FloatField()
special_month_price = models.FloatField()
def booking_days(self):
return (self.booking.end_date - self.booking.start_date).days + 1

View file

@ -14,12 +14,17 @@
<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">
{% bootstrap_form_errors form layout='inline' %}
<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">
{% bootstrap_form_errors form layout='inline' %}
{% for field in form %}
{% bootstrap_field field show_label=False %}
{% endfor %}
<!-- <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>

View file

@ -0,0 +1,80 @@
{% 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 Membership Order 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: {{membership_start_date|date}} - {{membership_end_date|date}}<br>
</h2>
<h2 class="col-xs-6 payment-total text-left">Membership month {{order.created_at|date:"F"}}</h2>
<h2 class="order-sum">{{order.amount|floatformat}}CHF</h2>
<hr class="greyline-long">
<h2 class="col-xs-6 payment-total text-left"> Total</h2>
<h2 class="order-result">{{order.amount|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: {{membership_start_date|date}} - {{membership_end_date|date}}<br><br></h2>
<h2 class="col-xs-6 payment-total">Membership month {{order.created_at|date:"F"}}</h2>
<h2 class="order-sum">{{order.amount|floatformat}}CHF</h2>
<hr class="greyline">
<h2 class="col-xs-6 payment-total">Total</h2>
<h2 class="order-result">{{order.amount|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 %}

View file

@ -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">{{membership_start_date|date}}-{{membership_end_date|date}}</h2>
<hr class="greyline-long">
<h2 class="order-head">Booking history</h2>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Valid Month</th>
<th>Date</th>
<th>Invoice</th>
</tr>
</thead>
<tbody>
{% for order in orders%}
<tr>
<th scope="row">{{order.id}}</th>
<td>{{order.created_at|date:"F"}}</td>
<td>{{order.created_at|date}}</td>
<td><a class="btn btn-xs btn-primary btn-darkgrey" href="{% url 'digitalglarus:membership_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 %}

View file

@ -25,6 +25,8 @@
<link href="{% static 'digitalglarus/css/ungleich.css' %}" rel="stylesheet">
<link href="{% static 'digitalglarus/css/history.css' %}" rel="stylesheet">
<link href="{% static 'digitalglarus/css/price.css' %}" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.css" />
<!-- <link href="css/bootstrap.min.css" rel="stylesheet"> -->
<link href="{% static 'digitalglarus/css/lib/animate.min.css' %}" rel="stylesheet">
<!-- <link href="{% static 'css/membership.css' %}" rel="stylesheet"> -->
@ -53,7 +55,32 @@
</script>
<link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon">
<style id="igtranslator-color" type="text/css"></style></head>
<style id="igtranslator-color" type="text/css"></style>
<style type="text/css">
.navbar-default .nav .dropdown.open .dropdown-toggle {
background: none !important;
color: white !important;
}
.navbar-default .nav .dropdown li a{
color:#0f1221;
text-transform: capitalize;
}
.navbar-default .nav li a .glyphicon-user{
font-size: 15px;
display: inline-block;
margin: 0px;
color:white;
}
</style>
</head>
<body id="page-top" class="index">
@ -89,13 +116,39 @@
<li>
<a class="page-scroll" href="#contact">Contact</a>
</li>
{% if request.user.is_authenticated %}
<li class="dropdown home-dropdown">
<a class="dropdown-toggle" role="button" data-toggle="dropdown" href="#">
<i class="glyphicon glyphicon-user"></i>{{request.user.name}} <span class="caret"></span>
</a>
<ul id="g-account-menu" class="dropdown-menu" role="menu">
<li>
<a class="page-scroll" href="{% url 'digitalglarus:login' %}">Log In</a>
<a href="{% url 'digitalglarus:booking_orders_list' %}">
<i class="fa fa-home" aria-hidden="true"></i> {% trans "Bookings"%}
</a>
</li>
<li>
<a class="page-scroll" href="{% url 'digitalglarus:signup' %}">Sign Up</a>
<a href="{% url 'digitalglarus:membership_orders_list' %}"><i class="fa fa-heart-o" aria-hidden="true"></i> {% trans "Membership"%}
</a>
</li>
<li>
<a href="{% url 'digitalglarus:logout' %}">
<i class="fa fa-lock" aria-hidden="true"></i>
{% trans "Logout"%}
</a>
</li>
</ul>
</li>
{% else %}
<li>
<a class="page-scroll" href="{% url 'digitalglarus:login' %}">Login</a>
</li>
{% endif %}
<!-- <li>
<a class="page-scroll" href="{% url 'digitalglarus:signup' %}">Sign Up</a>
</li>
--> </ul>
</div>
<!-- /.navbar-collapse -->
</div>
@ -174,8 +227,6 @@
-->
<!-- 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>

View file

@ -5,7 +5,7 @@ from . import views
from .views import ContactView, IndexView, AboutView, HistoryView, LoginView, SignupView,\
PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView,\
MembershipPricingView, BookingSelectDatesView, BookingPaymentView, OrdersBookingDetailView,\
BookingOrdersListView
BookingOrdersListView, MembershipOrdersListView, OrdersMembershipDetailView
# from membership.views import LoginRegistrationView
urlpatterns = [
@ -30,6 +30,10 @@ urlpatterns = [
name='membership_activated'),
url(_(r'membership/pricing/?$'), MembershipPricingView.as_view(),
name='membership_pricing'),
url(_(r'membership/orders/(?P<pk>\d+)/?$'), OrdersMembershipDetailView.as_view(),
name='membership_orders_detail'),
url(_(r'membership/orders/?$'), MembershipOrdersListView.as_view(),
name='membership_orders_list'),
url(_(r'supporters/?$'), views.supporters, name='supporters'),
url(r'calendar_api/(?P<month>\d+)/(?P<year>\d+)?$', views.CalendarApi.as_view(),name='calendar_api_1'),
url(r'calendar_api/', views.CalendarApi.as_view(),name='calendar_api'),

View file

@ -34,7 +34,7 @@ from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm
from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\
BookingOrder
from .mixins import MembershipRequired
from .mixins import MembershipRequiredMixin, IsNotMemberMixin
class IndexView(TemplateView):
@ -44,7 +44,7 @@ class IndexView(TemplateView):
class LoginView(LoginViewMixin):
template_name = "digitalglarus/login.html"
form_class = LoginForm
success_url = reverse_lazy('digitalglarus:landing')
success_url = reverse_lazy('digitalglarus:membership_pricing')
class SignupView(SignupViewMixin):
@ -77,7 +77,7 @@ class HistoryView(TemplateView):
return context
class BookingSelectDatesView(LoginRequiredMixin, MembershipRequired, FormView):
class BookingSelectDatesView(LoginRequiredMixin, MembershipRequiredMixin, FormView):
template_name = "digitalglarus/booking.html"
form_class = BookingDateForm
membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing')
@ -102,7 +102,7 @@ class BookingSelectDatesView(LoginRequiredMixin, MembershipRequired, FormView):
return super(BookingSelectDatesView, self).form_valid(form)
class BookingPaymentView(LoginRequiredMixin, MembershipRequired, FormView):
class BookingPaymentView(LoginRequiredMixin, MembershipRequiredMixin, FormView):
template_name = "digitalglarus/booking_payment.html"
form_class = BookingBillingForm
membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing')
@ -199,7 +199,10 @@ class BookingPaymentView(LoginRequiredMixin, MembershipRequired, FormView):
'booking': booking,
'customer': customer,
'billing_address': billing_address,
'stripe_charge': charge
'stripe_charge': charge,
'amount': final_price,
'original_price': normal_price,
'special_month_price': BookingPrice.objects.last().special_month_price
}
order = BookingOrder.create(order_data)
@ -208,8 +211,6 @@ class BookingPaymentView(LoginRequiredMixin, MembershipRequired, FormView):
# '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'))
class MembershipPricingView(TemplateView):
@ -224,10 +225,11 @@ class MembershipPricingView(TemplateView):
return context
class MembershipPaymentView(LoginRequiredMixin, FormView):
class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView):
template_name = "digitalglarus/membership_payment.html"
login_url = reverse_lazy('digitalglarus:signup')
form_class = MembershipBillingForm
already_member_redirect_url = reverse_lazy('digitalglarus:membership_orders_list')
def get_form_kwargs(self):
self.membership_type = MembershipType.objects.get(name='standard')
@ -291,7 +293,8 @@ class MembershipPaymentView(LoginRequiredMixin, FormView):
'membership': membership,
'customer': customer,
'billing_address': billing_address,
'stripe_charge': charge
'stripe_charge': charge,
'amount': membership_type.first_month_price
}
MembershipOrder.create(order_data)
@ -319,6 +322,45 @@ class MembershipActivatedView(TemplateView):
return context
class MembershipOrdersListView(LoginRequiredMixin, ListView):
template_name = "digitalglarus/membership_orders_list.html"
context_object_name = "orders"
login_url = reverse_lazy('digitalglarus:login')
model = MembershipOrder
paginate_by = 10
def get_context_data(self, **kwargs):
context = super(MembershipOrdersListView, self).get_context_data(**kwargs)
start_date, end_date = MembershipOrder.current_membership(self.request.user)
context.update({
'membership_start_date': start_date,
'membership_end_date': end_date,
})
return context
def get_queryset(self):
queryset = super(MembershipOrdersListView, self).get_queryset()
queryset = queryset.filter(customer__user=self.request.user)
return queryset
class OrdersMembershipDetailView(LoginRequiredMixin, DetailView):
template_name = "digitalglarus/membership_orders_detail.html"
context_object_name = "order"
login_url = reverse_lazy('digitalglarus:login')
# permission_required = ['view_hostingorder']
model = MembershipOrder
def get_context_data(self, **kwargs):
context = super(OrdersMembershipDetailView, self).get_context_data(**kwargs)
start_date, end_date = self.object.get_membership_range_date()
context.update({
'membership_start_date': start_date,
'membership_end_date': end_date,
})
return context
class OrdersBookingDetailView(LoginRequiredMixin, DetailView):
template_name = "digitalglarus/booking_orders_detail.html"
context_object_name = "order"