Merge pull request #56 from levivm/feature/vm_pricing

Added pagination to orders view, Created, Virtual machines booked view
This commit is contained in:
Levi Velázquez 2016-05-03 01:03:17 -05:00
commit ec5cecb7c3
12 changed files with 289 additions and 116 deletions

8
hosting/managers.py Normal file
View file

@ -0,0 +1,8 @@
from django.db import models
class VMPlansManager(models.Manager):
def active(self, user, **kwargs):
return self.select_related('hostingorder__customer__user').\
filter(hostingorder__customer__user=user, hostingorder__approved=True, **kwargs)

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-05-01 18:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0011_auto_20160426_0555'),
]
operations = [
migrations.AddField(
model_name='hostingorder',
name='cc_brand',
field=models.CharField(default='Visa', max_length=10),
preserve_default=False,
),
migrations.AddField(
model_name='hostingorder',
name='last4',
field=models.CharField(default=1111, max_length=4),
preserve_default=False,
),
]

View file

@ -2,10 +2,13 @@ import json
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.functional import cached_property
from django.core import serializers from django.core import serializers
from membership.models import StripeCustomer from membership.models import StripeCustomer
from utils.models import BillingAddress from utils.models import BillingAddress
from .managers import VMPlansManager
class RailsBetaUser(models.Model): class RailsBetaUser(models.Model):
email = models.EmailField(unique=True) email = models.EmailField(unique=True)
@ -81,6 +84,12 @@ class VirtualMachinePlan(models.Model):
vm_type = models.ForeignKey(VirtualMachineType) vm_type = models.ForeignKey(VirtualMachineType)
price = models.FloatField() price = models.FloatField()
objects = VMPlansManager()
@cached_property
def hosting_company_name(self):
return self.vm_type.get_hosting_company_display()
@classmethod @classmethod
def create(cls, data, user): def create(cls, data, user):
instance = cls.objects.create(**data) instance = cls.objects.create(**data)
@ -88,13 +97,23 @@ class VirtualMachinePlan(models.Model):
class HostingOrder(models.Model): class HostingOrder(models.Model):
ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined'
VMPlan = models.OneToOneField(VirtualMachinePlan) VMPlan = models.OneToOneField(VirtualMachinePlan)
customer = models.ForeignKey(StripeCustomer) customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress) billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False) 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) stripe_charge_id = models.CharField(max_length=100, null=True)
@cached_property
def status(self):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod @classmethod
def create(cls, VMPlan=None, customer=None, billing_address=None): def create(cls, VMPlan=None, customer=None, billing_address=None):
instance = cls.objects.create(VMPlan=VMPlan, customer=customer, instance = cls.objects.create(VMPlan=VMPlan, customer=customer,
@ -107,6 +126,8 @@ class HostingOrder(models.Model):
def set_stripe_charge(self, stripe_charge): def set_stripe_charge(self, stripe_charge):
self.stripe_charge_id = stripe_charge.id self.stripe_charge_id = stripe_charge.id
self.last4 = stripe_charge.source.last4
self.cc_brand = stripe_charge.source.brand
self.save() self.save()

View file

@ -0,0 +1,15 @@
.dashboard-container {
padding-top:5%; padding-bottom: 11%;
}
.borderless td {
border: none !important;
}
.borderless thead {
}
.borderless tbody:before {
content: "-";
display: block;
color: transparent;
}

View file

@ -0,0 +1,17 @@
.order-detail-container {padding-top:5%; padding-bottom: 11%;}
.order-detail-container .invoice-title h2, .invoice-title h3 {
display: inline-block;
}
.order-detail-container .table > tbody > tr > .no-line {
border-top: none;
}
.order-detail-container .table > thead > tr > .no-line {
border-bottom: none;
}
.order-detail-container .table > tbody > tr > .thick-line {
border-top: 2px solid;
}

View file

@ -0,0 +1,5 @@
.orders-container {padding-top:5%; padding-bottom: 11%;}
.orders-container .table > tbody > tr > td {
vertical-align: middle;
}

View file

@ -18,6 +18,9 @@
<!-- Custom CSS --> <!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/payment.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/payment.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/orders.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/orders.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/commons.css' %}" rel="stylesheet">
<!-- Custom Fonts --> <!-- Custom Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
@ -49,11 +52,33 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand topnav" href="#"><img src="img/logo_black.svg"></a> <a class="navbar-brand topnav" href="#"><img src="{% static 'hosting/img/logo_black.svg' %}"></a>
</div> </div>
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li>
<a href="{% url 'hosting:virtual_machines' %}">
<i class="fa fa-server" aria-hidden="true"></i> My Virtual Machines
</a>
</li>
<li>
<a href="{% url 'hosting:orders' %}">
<i class="fa fa-credit-card"></i> My Orders
</a>
</li>
<li class="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 href="{% url 'hosting:logout' %}"><i class="glyphicon glyphicon-lock"></i> Logout</a></li>
</ul>
</li>
{% else %}
<li> <li>
<a href="{{ request.META.HTTP_REFERER }}#how">How it works</a> <a href="{{ request.META.HTTP_REFERER }}#how">How it works</a>
</li> </li>
@ -69,9 +94,8 @@
<li> <li>
<a href="{{ request.META.HTTP_REFERER }}#contact">Contact</a> <a href="{{ request.META.HTTP_REFERER }}#contact">Contact</a>
</li> </li>
{% if request.user.is_authenticated %}
<li> <li>
<a href="{% url 'hosting:logout' %}"> Logout</a> <a href="{% url 'hosting:login' %}?next={{request.current_path}}">Login</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View file

@ -2,55 +2,37 @@
{% load staticfiles bootstrap3 %} {% load staticfiles bootstrap3 %}
{% block content %} {% block content %}
<style type="text/css"> <div class="container order-detail-container">
.invoice-title h2, .invoice-title h3 {
display: inline-block;
}
.table > tbody > tr > .no-line {
border-top: none;
}
.table > thead > tr > .no-line {
border-bottom: none;
}
.table > tbody > tr > .thick-line {
border-top: 2px solid;
}
</style>
<div class="container payment-container">
<div class="row"> <div class="row">
<div class="col-xs-8 col-xs-offset-2"> <div class="col-xs-8 col-xs-offset-2">
<div class="invoice-title"> <div class="invoice-title">
<h2>Invoice</h2><h3 class="pull-right">Order # {{order.id}}</h3> <h2>Invoice</h2><h3 class="pull-right">Order # {{object.id}}</h3>
</div> </div>
<hr> <hr>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<address> <address>
<h3><b>Billed To:</b></h3> <h3><b>Billed To:</b></h3>
John Smith<br> {{user.name}}<br>
1234 Main<br> {{object.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
Apt. 4B<br> {{object.billing_address.city}}, {{object.billing_address.country}}.
Springfield, ST 54321
</address> </address>
</div> </div>
<div class="col-xs-6 text-right"> <div class="col-xs-6 text-right">
<address> <address>
<strong>Order Date:</strong><br> <strong>Order Date:</strong><br>
{{order.created_at}}<br><br> {{object.created_at}}<br><br>
<strong>Status:</strong><br>
<strong class="text-danger">{{object.status}}</strong><br><br>
</address> </address>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<address> <address>
<strong>Payment Method:</strong><br> <strong>Payment Method:</strong><br>
{{brand}} ending **** {{last4}}<br> {{object.cc_brand}} ending **** {{object.last4}}<br>
{{user.email}} {{user.email}}
</address> </address>
</div> </div>
@ -63,15 +45,15 @@
<h3><b>Order summary</b></h3> <h3><b>Order summary</b></h3>
<hr> <hr>
<div class="content"> <div class="content">
<p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.hosting_company_name}}</span></p> <p><b>Type</b> <span class="pull-right">{{object.VMPlan.hosting_company_name}}</span></p>
<hr> <hr>
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p> <p><b>Cores</b> <span class="pull-right">{{object.VMPlan.cores}}</span></p>
<hr> <hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p> <p><b>Memory</b> <span class="pull-right">{{object.VMPlan.memory}} GiB</span></p>
<hr> <hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p> <p><b>Disk space</b> <span class="pull-right">{{object.VMPlan.disk_size}} GiB</span></p>
<hr> <hr>
<h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4> <h4>Total<p class="pull-right"><b>{{object.VMPlan.price}} CHF</b></p></h4>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,31 +1,75 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %} {% load staticfiles bootstrap3 %}
{% block content %} {% block content %}
<style type="text/css">
.borderless td {
border: none !important;
}
.borderless thead {
}
.borderless tbody:before {
content: "-";
display: block;
color: transparent;
}
</style>
<div> <div>
<div class="container payment-container"> <div class="container orders-container">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
<table class="table"> <table class="table borderless table-hover">
<caption>My orders.</caption> <h3><i class="fa fa-credit-card"></i> My Orders</h3>
<br/>
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>Date</th> <th>Date</th>
<th>Amount</th> <th>Amount</th>
<th>Status</th> <th>Status</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for order in orders %} {% for order in orders %}
<tr> <tr>
<th scope="row">{{order.id}}</th> <td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td> <td>{{order.created_at}}</td>
<td>{{order.VMPlan.price}}</td> <td>{{order.VMPlan.price}} CHF</td>
<td>{{order.approved}}</td> <td>{% if order.approved %}
<span class="text-success strong">Approved</span>
{% else%}
<span class="text-danger strong">Declined</span>
{% endif%}
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:orders' order.id %}">View Detail</a></button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
</div> </div>
</div> </div>

View file

@ -0,0 +1,56 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% block content %}
<div>
<div class="container dashboard-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover">
<h3><i class="fa fa-server" aria-hidden="true"></i> Virtual Machines</h3>
<br/>
<thead>
<tr>
<th>ID</th>
<th>Type</th>
<th>Amount</th>
<th></th>
</tr>
</thead>
<tbody>
{% for vm in vms %}
<tr>
<td scope="row">{{vm.id}}</td>
<td>{{vm.hosting_company_name}}</td>
<td>{{vm.price}} CHF</td>
<td>
<button type="button" class="btn btn-default"><a href="">View Detail</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -2,7 +2,7 @@ from django.conf.urls import url
from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \ from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
NodeJSHostingView, LoginView, SignupView, IndexView, \ NodeJSHostingView, LoginView, SignupView, IndexView, \
InvoiceVMView, OrdersHostingView OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
@ -10,8 +10,9 @@ urlpatterns = [
url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'), url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'),
url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'), url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'),
url(r'payment/?$', PaymentVMView.as_view(), name='payment'), url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'invoice/?$', InvoiceVMView.as_view(), name='invoice'), url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/?$', OrdersHostingView.as_view(), name='orders'), url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
url(r'login/?$', LoginView.as_view(), name='login'), url(r'login/?$', LoginView.as_view(), name='login'),
url(r'signup/?$', SignupView.as_view(), name='signup'), url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'^logout/?$', 'django.contrib.auth.views.logout', url(r'^logout/?$', 'django.contrib.auth.views.logout',

View file

@ -2,16 +2,12 @@
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import View, CreateView, FormView from django.views.generic import View, CreateView, FormView, ListView, DetailView
from django.shortcuts import redirect
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.conf import settings from django.conf import settings
from membership.forms import PaymentForm
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm from utils.forms import BillingAddressForm
@ -21,7 +17,6 @@ from .forms import HostingUserSignupForm, HostingUserLoginForm
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
class DjangoHostingView(ProcessVMSelectionMixin, View): class DjangoHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/django.html" template_name = "hosting/django.html"
@ -217,60 +212,38 @@ class PaymentVMView(FormView):
'order': order.id, 'order': order.id,
'billing_address': billing_address.id 'billing_address': billing_address.id
}) })
return HttpResponseRedirect(reverse('hosting:invoice')) return HttpResponseRedirect(reverse('hosting:orders', kwargs={'pk': order.id}))
else: else:
return self.form_invalid(form) return self.form_invalid(form)
class InvoiceVMView(LoginRequiredMixin, View): class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
template_name = "hosting/invoice.html" template_name = "hosting/order_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
model = HostingOrder
def get_context_data(self, **kwargs):
charge = self.request.session.get('charge')
order_id = self.request.session.get('order')
billing_address_id = self.request.session.get('billing_address')
last4 = charge.get('source').get('last4')
brand = charge.get('source').get('brand')
order = get_object_or_404(HostingOrder, pk=order_id)
billing_address = get_object_or_404(BillingAddress, pk=billing_address_id)
if not charge:
return
context = {
'last4': last4,
'brand': brand,
'order': order,
'billing_address': billing_address,
}
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return render(request, self.template_name, context)
class OrdersHostingView(LoginRequiredMixin, View): class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html" template_name = "hosting/orders.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
context_object_name = "orders"
model = HostingOrder
paginate_by = 10
def get_context_data(self, **kwargs): def get_queryset(self):
user = self.request.user user = self.request.user
orders = HostingOrder.objects.filter(customer__user=user) self.queryset = HostingOrder.objects.filter(customer__user=user)
context = { return super(OrdersHostingListView, self).get_queryset()
'orders':orders
}
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return render(request, self.template_name, context)
class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
template_name = "hosting/virtual_machines.html"
login_url = reverse_lazy('hosting:login')
context_object_name = "vms"
model = VirtualMachinePlan
paginate_by = 10
def get_queryset(self):
user = self.request.user
self.queryset = VirtualMachinePlan.objects.active(user)
return super(VirtualMachinesPlanListView, self).get_queryset()